2017-10-29 06:21:18 +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
{
2018-04-26 17:30:03 +00:00
private string ScrapeUrl { get { return SiteLink + "scrape.php" ; } }
private string TorrentsUrl { get { return SiteLink + "torrents.php" ; } }
2017-04-15 08:45:10 +00:00
public bool AllowRaws { get { return configData . IncludeRaw . Value ; } }
2017-07-10 20:58:44 +00:00
public bool InsertSeason { get { return configData . InsertSeason ! = null & & configData . InsertSeason . Value ; } }
2017-10-17 09:15:09 +00:00
public bool AddSynonyms { get { return configData . AddSynonyms . Value ; } }
public bool FilterSeasonEpisode { get { return 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
{
get { return ( ConfigurationDataAnimeBytes ) base . configData ; }
set { base . configData = value ; }
}
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-06 00:36:53 +00:00
configData : new ConfigurationDataAnimeBytes ( "Note about Passkey: This is not your login Password. Find the Passkey by logging into AnimeBytes with your browser" ) )
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)
2017-04-15 08:45:10 +00:00
}
2017-07-10 20:58:44 +00:00
protected override IEnumerable < ReleaseInfo > FilterResults ( TorznabQuery query , IEnumerable < ReleaseInfo > input )
2017-04-15 08:45:10 +00:00
{
// Prevent filtering
return input ;
}
2017-11-06 13:29:46 +00:00
public override async Task < IndexerConfigurationStatus > ApplyConfiguration ( JToken configJson )
{
LoadValuesFromJson ( configJson ) ;
2018-04-27 14:32:26 +00:00
if ( configData . Passkey . Value . Length ! = 32 )
throw new Exception ( "invalid passkey configured: expected length: 32, 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 ) ;
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 cat = "0" ;
var queryCats = MapTorznabCapsToTrackers ( query ) ;
if ( queryCats . Count = = 1 )
2017-04-15 08:45:10 +00:00
{
2018-04-26 17:30:03 +00:00
cat = queryCats . First ( ) . ToString ( ) ;
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 ( ) ;
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
2018-04-27 14:52:21 +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 Artists = ( string ) group [ "Artists" ] ;
var mainTitle = WebUtility . HtmlDecode ( ( string ) group [ "FullName" ] ) ;
if ( SeriesName ! = null )
mainTitle = SeriesName ;
synonyms . Add ( mainTitle ) ;
// If the title contains a comma then we can't use the synonyms as they are comma seperated
if ( ! mainTitle . Contains ( "," ) & & AddSynonyms )
2017-04-15 08:45:10 +00:00
{
2018-04-27 14:52:21 +00:00
var symnomnNames = WebUtility . HtmlDecode ( ( string ) group [ "Synonymns" ] ) ;
if ( ! string . IsNullOrWhiteSpace ( symnomnNames ) )
2017-04-15 08:45:10 +00:00
{
2018-04-27 14:52:21 +00:00
foreach ( var name in symnomnNames . Split ( "," . ToCharArray ( ) , StringSplitOptions . RemoveEmptyEntries ) )
2017-04-15 08:45:10 +00:00
{
2018-04-27 14:52:21 +00:00
var theName = name . Trim ( ) ;
if ( ! theName . Contains ( "&#" ) & & ! string . IsNullOrWhiteSpace ( theName ) )
{
synonyms . Add ( theName ) ;
}
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 ) ;
Regex SeasonRegEx = new Regex ( @"Season (\d+)" , RegexOptions . Compiled ) ;
var SeasonRegExMatch = SeasonRegEx . Match ( releaseInfo ) ;
if ( SeasonRegExMatch . Success )
season = ParseUtil . CoerceInt ( SeasonRegExMatch . Groups [ 1 ] . Value ) ;
Regex EpisodeRegEx = new Regex ( @"Episode (\d+)" , RegexOptions . Compiled ) ;
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 ( ) ;
int test = 0 ;
if ( InsertSeason & & int . TryParse ( releaseInfo , out test ) & & releaseInfo . Length < = 3 )
{
releaseInfo = "E0" + releaseInfo ;
}
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" )
{
if ( GroupName = = "TV Series" )
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
2018-04-27 14:52:21 +00:00
if ( GroupName = = "Movie" )
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 ( ) ;
for ( int 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 ;
}
2017-04-15 08:45:10 +00:00
2018-04-27 14:52:21 +00:00
var infoString = "" ;
2017-04-15 08:45:10 +00:00
2018-04-27 14:52:21 +00:00
for ( int i = 0 ; i + 1 < releaseTags . Count ( ) ; i + + )
2018-04-26 17:30:03 +00:00
{
2018-04-27 14:52:21 +00:00
if ( releaseTags [ i ] = = "Raw" & & ! AllowRaws )
continue ;
infoString + = "[" + releaseTags [ i ] + "]" ;
2017-04-15 08:45:10 +00:00
}
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
2018-04-27 14:52:21 +00:00
var release = new ReleaseInfo ( ) ;
release . MinimumRatio = 1 ;
release . MinimumSeedTime = MinimumSeedTime ;
release . Title = releaseTitle ;
release . Comments = CommentsLinkUri ;
release . Guid = new Uri ( CommentsLinkUri + "&nh=" + StringUtil . Hash ( title ) ) ; // Sonarr should dedupe on this url - allow a url per name.
release . Link = LinkUri ;
release . BannerUrl = ImageUrl ;
release . PublishDate = PublushDate ;
release . Category = Category ;
release . Description = Description ;
release . Size = Size ;
release . Seeders = Seeders ;
release . Peers = Peers ;
release . Grabs = Snatched ;
release . Files = FileCount ;
release . DownloadVolumeFactor = RawDownMultiplier ;
release . UploadVolumeFactor = RawUpMultiplier ;
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 ( ) ) ;
}
}
}