@ -1,13 +1,16 @@
using System ;
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.Linq ;
using System.Net ;
using System.Text ;
using System.Threading.Tasks ;
using FluentValidation ;
using FluentValidation.Results ;
using Newtonsoft.Json ;
using Newtonsoft.Json.Linq ;
using NLog ;
using NzbDrone.Common.Extensions ;
using NzbDrone.Common.Http ;
using NzbDrone.Common.Serializer ;
using NzbDrone.Core.Annotations ;
@ -16,6 +19,7 @@ using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Indexers.Settings ;
using NzbDrone.Core.IndexerSearch.Definitions ;
using NzbDrone.Core.Messaging.Events ;
using NzbDrone.Core.Parser ;
using NzbDrone.Core.Parser.Model ;
using NzbDrone.Core.Validation ;
@ -24,7 +28,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public class GazelleGames : TorrentIndexerBase < GazelleGamesSettings >
{
public override string Name = > "GazelleGames" ;
public override string [ ] IndexerUrls = > new string [ ] { "https://gazellegames.net/" } ;
public override string [ ] IndexerUrls = > new [ ] { "https://gazellegames.net/" } ;
public override string Description = > "GazelleGames (GGn) is a Private Torrent Tracker for GAMES" ;
public override string Language = > "en-US" ;
public override Encoding Encoding = > Encoding . UTF8 ;
@ -38,7 +42,7 @@ namespace NzbDrone.Core.Indexers.Definitions
public override IIndexerRequestGenerator GetRequestGenerator ( )
{
return new GazelleGamesRequestGenerator ( ) { Settings = Settings , Capabilities = Capabilities , HttpClient = _httpClient } ;
return new GazelleGamesRequestGenerator ( Settings , Capabilities ) ;
}
public override IParseIndexerResponse GetParser ( )
@ -48,14 +52,13 @@ namespace NzbDrone.Core.Indexers.Definitions
private IndexerCapabilities SetCapabilities ( )
{
var caps = new IndexerCapabilities
{
} ;
var caps = new IndexerCapabilities ( ) ;
// Apple
caps . Categories . AddCategoryMapping ( "Mac" , NewznabStandardCategory . ConsoleOther , "Mac" ) ;
caps . Categories . AddCategoryMapping ( "iOS" , NewznabStandardCategory . PCMobileiOS , "iOS" ) ;
caps . Categories . AddCategoryMapping ( "Apple Bandai Pippin" , NewznabStandardCategory . ConsoleOther , "Apple Bandai Pippin" ) ;
caps . Categories . AddCategoryMapping ( "Apple II" , NewznabStandardCategory . ConsoleOther , "Apple II" ) ;
// Google
caps . Categories . AddCategoryMapping ( "Android" , NewznabStandardCategory . PCMobileAndroid , "Android" ) ;
@ -78,6 +81,7 @@ namespace NzbDrone.Core.Indexers.Definitions
caps . Categories . AddCategoryMapping ( "Nintendo GameCube" , NewznabStandardCategory . ConsoleOther , "Nintendo GameCube" ) ;
caps . Categories . AddCategoryMapping ( "Pokemon Mini" , NewznabStandardCategory . ConsoleOther , "Pokemon Mini" ) ;
caps . Categories . AddCategoryMapping ( "SNES" , NewznabStandardCategory . ConsoleOther , "SNES" ) ;
caps . Categories . AddCategoryMapping ( "Switch" , NewznabStandardCategory . ConsoleOther , "Switch" ) ;
caps . Categories . AddCategoryMapping ( "Virtual Boy" , NewznabStandardCategory . ConsoleOther , "Virtual Boy" ) ;
caps . Categories . AddCategoryMapping ( "Wii" , NewznabStandardCategory . ConsoleWii , "Wii" ) ;
caps . Categories . AddCategoryMapping ( "Wii U" , NewznabStandardCategory . ConsoleWiiU , "Wii U" ) ;
@ -178,31 +182,62 @@ namespace NzbDrone.Core.Indexers.Definitions
caps . Categories . AddCategoryMapping ( "Retro - Other" , NewznabStandardCategory . ConsoleOther , "Retro - Other" ) ;
// special categories (real categories/not platforms)
caps . Categories . AddCategoryMapping ( "OST" , NewznabStandardCategory . AudioOther , "OST" ) ;
caps . Categories . AddCategoryMapping ( "Applications" , NewznabStandardCategory . PC0day , "Applications" ) ;
caps . Categories . AddCategoryMapping ( "E-Books" , NewznabStandardCategory . BooksEBook , "E-Books" ) ;
caps . Categories . AddCategoryMapping ( 1 , NewznabStandardCategory . PCGames , "Games" ) ;
caps . Categories . AddCategoryMapping ( 2 , NewznabStandardCategory . PC0day , "Applications" ) ;
caps . Categories . AddCategoryMapping ( 3 , NewznabStandardCategory . BooksEBook , "E-Books" ) ;
caps . Categories . AddCategoryMapping ( 4 , NewznabStandardCategory . AudioOther , "OST" ) ;
return caps ;
}
protected override async Task Test ( List < ValidationFailure > failures )
{
( ( GazelleGamesRequestGenerator ) GetRequestGenerator ( ) ) . FetchPasskey ( ) ;
await base . Test ( failures ) ;
await FetchPasskey ( ) . ConfigureAwait ( false ) ;
await base . Test ( failures ) . ConfigureAwait ( false ) ;
}
private async Task FetchPasskey ( )
{
var request = new HttpRequestBuilder ( $"{Settings.BaseUrl.Trim().TrimEnd('/')}/api.php" )
. Accept ( HttpAccept . Json )
. SetHeader ( "X-API-Key" , Settings . Apikey )
. AddQueryParam ( "request" , "quick_user" )
. Build ( ) ;
var indexResponse = await _httpClient . ExecuteAsync ( request ) . ConfigureAwait ( false ) ;
var index = Json . Deserialize < GazelleGamesUserResponse > ( indexResponse . Content ) ;
if ( index = = null | |
string . IsNullOrWhiteSpace ( index . Status ) | |
index . Status ! = "success" | |
string . IsNullOrWhiteSpace ( index . Response . PassKey ) )
{
throw new IndexerAuthException ( "Failed to authenticate with GazelleGames." ) ;
}
// Set passkey on settings so it can be used to generate the download URL
Settings . Passkey = index . Response . PassKey ;
}
}
public class GazelleGamesRequestGenerator : IIndexerRequestGenerator
{
public GazelleGamesSettings Settings { get ; set ; }
public IndexerCapabilities Capabilities { get ; set ; }
public IIndexerHttpClient HttpClient { get ; set ; }
private readonly GazelleGamesSettings _settings ;
private readonly IndexerCapabilities _capabilities ;
public GazelleGamesRequestGenerator ( GazelleGamesSettings settings , IndexerCapabilities capabilities )
{
_settings = settings ;
_capabilities = capabilities ;
}
public IndexerPageableRequestChain GetSearchRequests ( MovieSearchCriteria searchCriteria )
{
var pageableRequests = new IndexerPageableRequestChain ( ) ;
pageableRequests . Add ( GetRequest ( GetBasicSearchParameters ( searchCriteria . SanitizedSearchTerm , searchCriteria . Categories ) ) ) ;
pageableRequests . Add ( GetRequest ( GetBasicSearchParameters ( searchCriteria . SanitizedSearchTerm , searchCriteria )) ) ;
return pageableRequests ;
}
@ -211,7 +246,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain ( ) ;
pageableRequests . Add ( GetRequest ( GetBasicSearchParameters ( searchCriteria . SanitizedSearchTerm , searchCriteria .Categories )) ) ;
pageableRequests . Add ( GetRequest ( GetBasicSearchParameters ( searchCriteria . SanitizedSearchTerm , searchCriteria )) ) ;
return pageableRequests ;
}
@ -220,7 +255,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain ( ) ;
pageableRequests . Add ( GetRequest ( GetBasicSearchParameters ( searchCriteria . SanitizedSearchTerm , searchCriteria .Categories )) ) ;
pageableRequests . Add ( GetRequest ( GetBasicSearchParameters ( searchCriteria . SanitizedSearchTerm , searchCriteria )) ) ;
return pageableRequests ;
}
@ -229,7 +264,7 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain ( ) ;
pageableRequests . Add ( GetRequest ( GetBasicSearchParameters ( searchCriteria . SanitizedSearchTerm , searchCriteria .Categories )) ) ;
pageableRequests . Add ( GetRequest ( GetBasicSearchParameters ( searchCriteria . SanitizedSearchTerm , searchCriteria )) ) ;
return pageableRequests ;
}
@ -238,61 +273,72 @@ namespace NzbDrone.Core.Indexers.Definitions
{
var pageableRequests = new IndexerPageableRequestChain ( ) ;
pageableRequests . Add ( GetRequest ( GetBasicSearchParameters ( searchCriteria . SanitizedSearchTerm , searchCriteria .Categories )) ) ;
pageableRequests . Add ( GetRequest ( GetBasicSearchParameters ( searchCriteria . SanitizedSearchTerm , searchCriteria )) ) ;
return pageableRequests ;
}
p ublic void FetchPasskey ( )
p rivate IEnumerable < IndexerRequest > GetRequest ( List < KeyValuePair < string , string > > parameters )
{
// GET on index for the passkey
var request = RequestBuilder ( ) . Resource ( "api.php?request=quick_user" ) . Build ( ) ;
var indexResponse = HttpClient . Execute ( request ) ;
var index = Json . Deserialize < GazelleGamesUserResponse > ( indexResponse . Content ) ;
if ( index = = null | |
string . IsNullOrWhiteSpace ( index . Status ) | |
index . Status ! = "success" | |
string . IsNullOrWhiteSpace ( index . Response . PassKey ) )
{
throw new Exception ( "Failed to authenticate with GazelleGames." ) ;
}
// Set passkey on settings so it can be used to generate the download URL
Settings . Passkey = index . Response . PassKey ;
}
private IEnumerable < IndexerRequest > GetRequest ( string parameters )
{
var req = RequestBuilder ( )
. Resource ( $"api.php?{parameters}" )
var request = RequestBuilder ( )
. Resource ( $"/api.php?{parameters.GetQueryString()}" )
. Build ( ) ;
yield return new IndexerRequest ( req ) ;
yield return new IndexerRequest ( request ) ;
}
private HttpRequestBuilder RequestBuilder ( )
{
return new HttpRequestBuilder ( $"{Settings.BaseUrl.Trim().TrimEnd('/')}" )
return new HttpRequestBuilder ( $"{_settings.BaseUrl.Trim().TrimEnd('/')}" )
. Resource ( "/api.php" )
. Accept ( HttpAccept . Json )
. SetHeader ( "X-API-Key" , S ettings. Apikey ) ;
. SetHeader ( "X-API-Key" , _settings . Apikey ) ;
}
private string GetBasicSearchParameters ( string searchTerm , int [ ] categories )
private List < KeyValuePair < string , string > > GetBasicSearchParameters ( string searchTerm , SearchCriteriaBase searchCriteria )
{
var parameters = "request=search&search_type=torrents&empty_groups=filled&order_by=time&order_way=desc" ;
var parameters = new List < KeyValuePair < string , string > >
{
{ "request" , "search" } ,
{ "search_type" , "torrents" } ,
{ "empty_groups" , "filled" } ,
{ "order_by" , "time" } ,
{ "order_way" , "desc" }
} ;
if ( ! string . IsNullOrWhiteSpace ( searchTerm ) )
if ( searchTerm . IsNotNullOrWhiteSpace ( ) )
{
var searchType = Settings . SearchGroupNames ? "groupname" : "searchstr" ;
parameters . Add (
_settings . SearchGroupNames ? "groupname" : "searchstr" ,
searchTerm . Replace ( "." , " " ) ) ;
}
if ( searchCriteria . Categories ! = null )
{
var categoryMappings = _capabilities . Categories
. MapTorznabCapsToTrackers ( searchCriteria . Categories )
. Distinct ( )
. Where ( x = > ! x . IsAllDigits ( ) )
. ToList ( ) ;
parameters + = string . Format ( "&{1}={0}" , searchTerm . Replace ( "." , " " ) , searchType ) ;
categoryMappings. ForEach ( category = > parameters . Add ( "artistcheck[]" , category ) ) ;
}
if ( categories ! = null )
if ( searchCriteria. MinSize is > 0 )
{
foreach ( var cat in Capabilities . Categories . MapTorznabCapsToTrackers ( categories ) )
var minSize = searchCriteria . MinSize . Value / 1024L / 1024L ;
if ( minSize > 0 )
{
parameters + = string . Format ( "&artistcheck[]={0}" , cat ) ;
parameters . Add ( "sizesmall" , minSize . ToString ( ) ) ;
}
}
if ( searchCriteria . MaxSize is > 0 )
{
var maxSize = searchCriteria . MaxSize . Value / 1024L / 1024L ;
if ( maxSize > 0 )
{
parameters . Add ( "sizeslarge" , maxSize . ToString ( ) ) ;
}
}
@ -329,66 +375,63 @@ namespace NzbDrone.Core.Indexers.Definitions
}
var jsonResponse = new HttpResponse < GazelleGamesResponse > ( indexerResponse . HttpResponse ) ;
if ( jsonResponse . Resource . Status ! = "success" | |
string . IsNullOrWhiteSpace ( jsonResponse . Resource . Status ) | |
jsonResponse . Resource . Response = = null )
jsonResponse . Resource . Response is not JObject response )
{
return torrentInfos ;
}
Dictionary < string , GazelleGamesGroup > response ;
try
{
response = ( ( JObject ) jsonResponse . Resource . Response ) . ToObject < Dictionary < string , GazelleGamesGroup > > ( ) ;
}
catch
{
return torrentInfos ;
}
var groups = response . ToObject < Dictionary < int , GazelleGamesGroup > > ( JsonSerializer . Create ( Json . GetSerializerSettings ( ) ) ) ;
foreach ( var result in response )
foreach ( var group in groups )
{
Dictionary < string , GazelleGamesTorrent > torrents ;
try
{
torrents = ( ( JObject ) result . Value . Torrents ) . ToObject < Dictionary < string , GazelleGamesTorrent > > ( ) ;
}
catch
if ( group . Value . Torrents is not JObject groupTorrents )
{
continue ;
}
if ( result . Value . Torrents ! = null )
var torrents = groupTorrents
. ToObject < Dictionary < int , GazelleGamesTorrent > > ( JsonSerializer . Create ( Json . GetSerializerSettings ( ) ) )
. Where ( t = > t . Value . TorrentType ! = "Link" )
. ToList ( ) ;
var categories = group . Value . Artists
. SelectMany ( a = > _categories . MapTrackerCatDescToNewznab ( a . Name ) )
. Distinct ( )
. ToArray ( ) ;
foreach ( var torrent in torrents )
{
var categories = result . Value . Artists . Select ( a = > a . Name ) ;
var torrentId = torrent . Key ;
var infoUrl = GetInfoUrl ( group . Key , torrentId ) ;
foreach ( var torrent in torrents )
if ( categories . Length = = 0 )
{
var id = int . Parse ( torrent . Key ) ;
var infoUrl = GetInfoUrl ( result . Key , id ) ;
var release = new TorrentInfo ( )
{
Guid = infoUrl ,
Title = torrent . Value . ReleaseTitle ,
Files = torrent . Value . FileCount ,
Grabs = torrent . Value . Snatched ,
Size = long . Parse ( torrent . Value . Size ) ,
DownloadUrl = GetDownloadUrl ( id ) ,
InfoUrl = infoUrl ,
Seeders = torrent . Value . Seeders ,
Categories = _categories . MapTrackerCatDescToNewznab ( categories . FirstOrDefault ( ) ) ,
Peers = torrent . Value . Leechers + torrent . Value . Seeders ,
PublishDate = torrent . Value . Time . ToUniversalTime ( ) ,
DownloadVolumeFactor = torrent . Value . FreeTorrent = = GazelleGamesFreeTorrent . FreeLeech | | torrent . Value . FreeTorrent = = GazelleGamesFreeTorrent . Neutral | | torrent . Value . LowSeedFL ? 0 : 1 ,
UploadVolumeFactor = torrent . Value . FreeTorrent = = GazelleGamesFreeTorrent . Neutral ? 0 : 1
} ;
torrentInfos . Add ( release ) ;
categories = _categories . MapTrackerCatToNewznab ( torrent . Value . CategoryId . ToString ( ) ) . ToArray ( ) ;
}
var release = new TorrentInfo
{
Guid = infoUrl ,
InfoUrl = infoUrl ,
DownloadUrl = GetDownloadUrl ( torrentId ) ,
Title = GetTitle ( group . Value , torrent . Value ) ,
Categories = categories ,
Files = torrent . Value . FileCount ,
Size = long . Parse ( torrent . Value . Size ) ,
Grabs = torrent . Value . Snatched ,
Seeders = torrent . Value . Seeders ,
Peers = torrent . Value . Leechers + torrent . Value . Seeders ,
PublishDate = torrent . Value . Time . ToUniversalTime ( ) ,
Scene = torrent . Value . Scene = = 1 ,
DownloadVolumeFactor = torrent . Value . FreeTorrent is GazelleGamesFreeTorrent . FreeLeech or GazelleGamesFreeTorrent . Neutral | | torrent . Value . LowSeedFL ? 0 : 1 ,
UploadVolumeFactor = torrent . Value . FreeTorrent = = GazelleGamesFreeTorrent . Neutral ? 0 : 1 ,
MinimumSeedTime = 288000 // Minimum of 3 days and 8 hours (80 hours in total)
} ;
torrentInfos . Add ( release ) ;
}
}
@ -399,6 +442,44 @@ namespace NzbDrone.Core.Indexers.Definitions
. ToArray ( ) ;
}
private static string GetTitle ( GazelleGamesGroup group , GazelleGamesTorrent torrent )
{
var title = WebUtility . HtmlDecode ( torrent . ReleaseTitle ) ;
if ( group . Year is > 0 & & ! title . Contains ( group . Year . ToString ( ) ) )
{
title + = $" ({group.Year})" ;
}
if ( torrent . RemasterTitle . IsNotNullOrWhiteSpace ( ) )
{
title + = $" [{$" { torrent . RemasterTitle } { torrent . RemasterYear } ".Trim()}]" ;
}
var flags = new List < string >
{
$"{torrent.Format} {torrent.Encoding}" . Trim ( )
} ;
if ( group . Artists is { Count : > 0 } )
{
flags . AddIfNotNull ( group . Artists . Select ( a = > a . Name ) . Join ( ", " ) ) ;
}
flags . AddIfNotNull ( torrent . Language ) ;
flags . AddIfNotNull ( torrent . Region ) ;
flags . AddIfNotNull ( torrent . Miscellaneous ) ;
flags = flags . Where ( x = > x . IsNotNullOrWhiteSpace ( ) ) . ToList ( ) ;
if ( flags . Any ( ) )
{
title + = $" [{string.Join(" / ", flags)}]" ;
}
return title ;
}
private string GetDownloadUrl ( int torrentId )
{
// AuthKey is required but not checked, just pass in a dummy variable
@ -413,7 +494,7 @@ namespace NzbDrone.Core.Indexers.Definitions
return url . FullUri ;
}
private string GetInfoUrl ( string groupId , int torrentId )
private string GetInfoUrl ( int groupId , int torrentId )
{
var url = new HttpUri ( _settings . BaseUrl )
. CombinePath ( "/torrents.php" )
@ -444,7 +525,7 @@ namespace NzbDrone.Core.Indexers.Definitions
Passkey = "" ;
}
[FieldDefinition(2, Label = "API Key", HelpText = "API Key from the Site (Found in Settings => Access Settings) , Must have User P ermissions", Privacy = PrivacyLevel.ApiKey)]
[FieldDefinition(2, Label = "API Key", HelpText = "API Key from the Site (Found in Settings => Access Settings) ", HelpTextWarning = "Must have User and Torrents p ermissions", Privacy = PrivacyLevel.ApiKey)]
public string Apikey { get ; set ; }
[FieldDefinition(3, Label = "Search Group Names", Type = FieldType.Checkbox, HelpText = "Search Group Names Only")]
@ -466,8 +547,9 @@ namespace NzbDrone.Core.Indexers.Definitions
public class GazelleGamesGroup
{
public List < GazelleGamesArtist > Artists { get ; set ; }
public ReadOnlyCollection < GazelleGamesArtist > Artists { get ; set ; }
public object Torrents { get ; set ; }
public int? Year { get ; set ; }
}
public class GazelleGamesArtist
@ -478,13 +560,23 @@ namespace NzbDrone.Core.Indexers.Definitions
public class GazelleGamesTorrent
{
public int CategoryId { get ; set ; }
public string Format { get ; set ; }
public string Encoding { get ; set ; }
public string Language { get ; set ; }
public string Region { get ; set ; }
public string RemasterYear { get ; set ; }
public string RemasterTitle { get ; set ; }
public string ReleaseTitle { get ; set ; }
public string Miscellaneous { get ; set ; }
public int Scene { get ; set ; }
public DateTime Time { get ; set ; }
public string TorrentType { get ; set ; }
public int FileCount { get ; set ; }
public string Size { get ; set ; }
public int? Snatched { get ; set ; }
public int Seeders { get ; set ; }
public int Leechers { get ; set ; }
public string ReleaseTitle { get ; set ; }
public DateTime Time { get ; set ; }
public int FileCount { get ; set ; }
public GazelleGamesFreeTorrent FreeTorrent { get ; set ; }
public bool PersonalFL { get ; set ; }
public bool LowSeedFL { get ; set ; }
@ -503,9 +595,9 @@ namespace NzbDrone.Core.Indexers.Definitions
public enum GazelleGamesFreeTorrent
{
Normal ,
FreeLeech ,
Neutral ,
Either
Normal = 0 ,
FreeLeech = 1 ,
Neutral = 2 ,
Either = 3
}
}