Skip to content

Commit 76afb70

Browse files
committed
New: (Cardigann) Allow JSON filters
Fixes #844
1 parent c29fba3 commit 76afb70

File tree

5 files changed

+104
-18
lines changed

5 files changed

+104
-18
lines changed

src/NzbDrone.Core/IndexerVersions/IndexerDefinitionUpdateService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class IndexerDefinitionUpdateService : IIndexerDefinitionUpdateService, I
2828
/* Update Service will fall back if version # does not exist for an indexer per Ta */
2929

3030
private const string DEFINITION_BRANCH = "master";
31-
private const int DEFINITION_VERSION = 4;
31+
private const int DEFINITION_VERSION = 5;
3232

3333
//Used when moving yml to C#
3434
private readonly List<string> _defintionBlocklist = new List<string>()

src/NzbDrone.Core/Indexers/Definitions/Cardigann/CardigannBase.cs

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ public class CardigannBase
4848
protected static readonly Regex _LogicFunctionRegex = new Regex(
4949
$@"\b({string.Join("|", _SupportedLogicFunctions.Select(Regex.Escape))})(?:\s+(\(?\.[^\)\s]+\)?|""[^""]+"")){{2,}}");
5050

51+
// Matches CSS selectors for the JSON parser
52+
protected static readonly Regex _jsonSelectorRegex = new Regex(@"\:(?<filter>.+?)\((?<key>.+?)\)(?=:|\z)", RegexOptions.Compiled);
53+
5154
public CardigannSettings Settings { get; set; }
5255

5356
public CardigannBase(IConfigService configService,
@@ -234,13 +237,20 @@ protected string HandleJsonSelector(SelectorBlock selector, JToken parentObj, Di
234237

235238
if (selector.Selector != null)
236239
{
237-
var selector_Selector = ApplyGoTemplateText(selector.Selector.TrimStart('.'), variables);
238-
var selection = parentObj.SelectToken(selector_Selector);
240+
var selectorSelector = ApplyGoTemplateText(selector.Selector.TrimStart('.'), variables);
241+
selectorSelector = JsonParseFieldSelector(parentObj, selectorSelector);
242+
243+
JToken selection = null;
244+
if (selectorSelector != null)
245+
{
246+
selection = parentObj.SelectToken(selectorSelector);
247+
}
248+
239249
if (selection == null)
240250
{
241251
if (required)
242252
{
243-
throw new Exception(string.Format("Selector \"{0}\" didn't match {1}", selector_Selector, parentObj.ToString()));
253+
throw new Exception(string.Format("Selector \"{0}\" didn't match {1}", selectorSelector, parentObj.ToString()));
244254
}
245255

246256
return null;
@@ -851,5 +861,89 @@ protected string ResolveSiteLink()
851861

852862
return settingsBaseUrl;
853863
}
864+
865+
protected JArray JsonParseRowsSelector(JToken parsedJson, string rowSelector)
866+
{
867+
var selector = rowSelector.Split(':')[0];
868+
var rowsObj = parsedJson.SelectToken(selector).Value<JArray>();
869+
return new JArray(rowsObj.Where(t =>
870+
JsonParseFieldSelector(t.Value<JObject>(), rowSelector.Remove(0, selector.Length)) != null));
871+
}
872+
873+
private string JsonParseFieldSelector(JToken parsedJson, string rowSelector)
874+
{
875+
var selector = rowSelector.Split(':')[0];
876+
JToken parsedObject;
877+
if (string.IsNullOrWhiteSpace(selector))
878+
{
879+
parsedObject = parsedJson;
880+
}
881+
else if (parsedJson.SelectToken(selector) != null)
882+
{
883+
parsedObject = parsedJson.SelectToken(selector);
884+
}
885+
else
886+
{
887+
return null;
888+
}
889+
890+
foreach (Match match in _jsonSelectorRegex.Matches(rowSelector))
891+
{
892+
var filter = match.Result("${filter}");
893+
var key = match.Result("${key}");
894+
Match innerMatch;
895+
switch (filter)
896+
{
897+
case "has":
898+
innerMatch = _jsonSelectorRegex.Match(key);
899+
if (innerMatch.Success)
900+
{
901+
if (JsonParseFieldSelector(parsedObject, key) == null)
902+
{
903+
return null;
904+
}
905+
}
906+
else
907+
{
908+
if (parsedObject.SelectToken(key) == null)
909+
{
910+
return null;
911+
}
912+
}
913+
914+
break;
915+
case "not":
916+
innerMatch = _jsonSelectorRegex.Match(key);
917+
if (innerMatch.Success)
918+
{
919+
if (JsonParseFieldSelector(parsedObject, key) != null)
920+
{
921+
return null;
922+
}
923+
}
924+
else
925+
{
926+
if (parsedObject.SelectToken(key) != null)
927+
{
928+
return null;
929+
}
930+
}
931+
932+
break;
933+
case "contains":
934+
if (!parsedObject.ToString().Contains(key))
935+
{
936+
return null;
937+
}
938+
939+
break;
940+
default:
941+
_logger.Error(string.Format("CardigannIndexer ({0}): Unsupported selector: {1}", _definition.Id, rowSelector));
942+
continue;
943+
}
944+
}
945+
946+
return selector;
947+
}
854948
}
855949
}

src/NzbDrone.Core/Indexers/Definitions/Cardigann/CardigannDefinition.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ public class RowsBlock : SelectorBlock
151151
public int After { get; set; }
152152
public SelectorBlock Dateheaders { get; set; }
153153
public SelectorBlock Count { get; set; }
154+
public bool Multiple { get; set; } = false;
154155
}
155156

156157
public class SearchPathBlock : RequestBlock
@@ -200,8 +201,6 @@ public class BeforeBlock : RequestBlock
200201
public class ResponseBlock
201202
{
202203
public string Type { get; set; }
203-
public string Attribute { get; set; }
204-
public bool Multiple { get; set; }
205204
public string NoResultsMessage { get; set; }
206205
}
207206
}

src/NzbDrone.Core/Indexers/Definitions/Cardigann/CardigannParser.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,16 @@ public IList<ReleaseInfo> ParseResponse(IndexerResponse indexerResponse)
8383
}
8484
}
8585

86-
var rowsObj = parsedJson.SelectToken(search.Rows.Selector);
87-
if (rowsObj == null)
86+
var rowsArray = JsonParseRowsSelector(parsedJson, search.Rows.Selector);
87+
if (rowsArray == null)
8888
{
8989
throw new IndexerException(indexerResponse, "Error Parsing Rows Selector");
9090
}
9191

92-
foreach (var row in rowsObj.Value<JArray>())
92+
foreach (var row in rowsArray)
9393
{
94-
var selObj = request.SearchPath.Response.Attribute != null ? row.SelectToken(request.SearchPath.Response.Attribute).Value<JToken>() : row;
95-
var mulRows = request.SearchPath.Response.Multiple == true ? selObj.Values<JObject>() : new List<JObject> { selObj.Value<JObject>() };
94+
var selObj = search.Rows.Attribute != null ? row.SelectToken(search.Rows.Attribute).Value<JToken>() : row;
95+
var mulRows = search.Rows.Multiple ? selObj.Values<JObject>() : new List<JObject> { selObj.Value<JObject>() };
9696

9797
foreach (var mulRow in mulRows)
9898
{

src/NzbDrone.Core/Indexers/Definitions/Cardigann/CardigannRequest.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,6 @@ public class CardigannRequest : IndexerRequest
88
public Dictionary<string, object> Variables { get; private set; }
99
public SearchPathBlock SearchPath { get; private set; }
1010

11-
public CardigannRequest(string url, HttpAccept httpAccept, Dictionary<string, object> variables, SearchPathBlock searchPath)
12-
: base(url, httpAccept)
13-
{
14-
Variables = variables;
15-
SearchPath = searchPath;
16-
}
17-
1811
public CardigannRequest(HttpRequest httpRequest, Dictionary<string, object> variables, SearchPathBlock searchPath)
1912
: base(httpRequest)
2013
{

0 commit comments

Comments
 (0)