Compare commits

...

15 Commits

Author SHA1 Message Date
Boris Gerretzen bd8a23a9b3
Merge 8d3dfd43d4 into 8801ad4ec0 2024-04-28 01:28:25 +01:00
ilike2burnthing 8801ad4ec0
cpabien: update sitelink. resolves #15281 2024-04-28 01:20:58 +01:00
ilike2burnthing fc76687225
hdolimpo-api: update title filters. resolves #15279 2024-04-28 01:14:24 +01:00
ilike2burnthing 7b9f92b230
rudub: change subdomain 28* 2024-04-27 22:19:48 +01:00
Garfield69 42b8d52b5c uniongang: can have magnet instead of .torrent 2024-04-28 06:06:42 +12:00
Garfield69 14cc8e4dc0 torrent911: new domain 2024-04-28 05:08:41 +12:00
Bogdan 99c058c2a2 ygg: update base url 2024-04-27 14:08:10 +03:00
ilike2burnthing 5a287c04f5
danishbytes-api: remove old mirror
credit: @kbhsn4 - https://github.com/Prowlarr/Indexers/pull/441
2024-04-27 04:33:08 +01:00
ilike2burnthing c89350629c
torlock: add cert exception. resolves #1650 2024-04-27 03:00:21 +01:00
Boris 8d3dfd43d4 Redid xml to json conversion 2024-03-10 22:36:17 +01:00
Boris Gerretzen 4eaacaeb94
Merge branch 'Jackett:master' into master 2024-03-10 22:09:13 +01:00
Bogdan 86f5e5abce
Merge branch 'master' into master 2024-02-08 20:56:00 +02:00
Boris e4911054e3 Better formatting of caps as json 2024-02-08 18:48:02 +01:00
Boris 533a25da54 Fix linter error 2024-02-03 20:42:30 +01:00
Boris 23b62c2fa6 [enhancement]: support Torznab o=JSON 2024-02-03 20:33:09 +01:00
17 changed files with 309 additions and 54 deletions

View File

@ -7,7 +7,7 @@ type: public
encoding: UTF-8
followredirect: true
links:
- https://cpasbien-vf.li/
- https://feminalpes.fr/
legacylinks:
- https://www.cpasbiens.cz/
- https://www.cpasbiens.bz/
@ -26,6 +26,7 @@ legacylinks:
- https://jardattraction.fr/
- https://cpasbien-vf.fr/
- https://cpasbien-vf.pics/
- https://cpasbien-vf.li/
caps:
categories:

View File

@ -9,9 +9,9 @@ links:
- https://danishbytes.club/
- https://danishbytes2.org/
- https://dbytes.org/
- https://danishbytes.art/
legacylinks:
- https://danishbytes.org/
- https://danishbytes.art/
caps:
categorymappings:

View File

@ -93,63 +93,72 @@ search:
fields:
categorydesc:
selector: category
title:
title_phase1:
selector: name
filters:
- name: re_replace
args: ["\\[", " "]
args: ["(?i)\\b(full uhd|(uhd )?full\\s?(blu-?ray|uhd))\\b", "BRDISK"]
- name: re_replace
args: ["\\]", " "]
args: ["\\bE-AC-3\\b", "EAC3"]
- name: re_replace
args: ["(?i)(full(bluray)?)", "BRDISK"] # FULL(BR/UHD) -> BRDISK
- name: replace
args: ["HDOlimpo", ""] # Delete HDOlimpo
- name: replace
args: ["HD-Olimpo", ""] # Delete HD-Olimpo
- name: replace
args: ["E-AC-3", "EAC3"]
args: ["(?i)\\b(es-cat?-en|es-en-cat?|en-cat?-es|en-es-cat?|cat?-es-en|cat?-en-es)\\b", "MULTi SPANiSH CATALAN ENGLiSH"]
- name: re_replace
args: ["[ -](?i)español[ -]", " SPANiSH "]
args: ["(?i)\\b(es-cat?-(ja|ja?p)|es-(ja|ja?p)-cat?|cat?-es-(ja|ja?p)|cat?-(ja|ja?p)-es|(ja|ja?p)-es-cat?|(ja|ja?p)-cat?-es)\\b", "MULTi SPANiSH CATALAN JAPANES"] # misspelled to accomodate negative lookback, corrected later
- name: re_replace
args: ["[ -](?i)castellano[ -]", " SPANiSH "]
args: ["(?i)\\b(es-en|en-es)\\b", "MULTi SPANiSH ENGLiSH"]
- name: re_replace
args: ["[ -](?i)spa[ -]", " SPANiSH "]
args: ["(?i)\\b(es-cat?|cat?-es)\\b", "MULTi SPANiSH CATALAN"]
- name: re_replace
args: ["[ -](?i)esp[ -]", " SPANiSH "]
args: ["(?i)\\b(es-(ja|ja?p)|(ja|ja?p)-es)\\b", "MULTi SPANiSH JAPANES"] # misspelled to accomodate negative lookback, corrected later
- name: re_replace
args: [" ES ", " SPANiSH "]
args: ["(?i)\\b(es-fr[ae]n?|fr[ae]n?-es)\\b", "MULTi SPANiSH FRENCHx"] # misspelled to accomodate negative lookback, corrected later
- name: re_replace
args: ["[ -](?i)ingl[eé]s[ -]", " English "]
args: ["(?i)\\b(es-ita?|ita?-es)\\b", "MULTi SPANiSH iTALiAN"]
- name: re_replace
args: ["[ -](?i)[ei]ng[ -]", " English "]
args: ["(?i)\\b(es-rus?|rus?-es)\\b", "MULTi SPANiSH RUSSiAN"]
- name: re_replace
args: ["[ -](?i)cat[ -]", " Catalan "]
args: ["(?i)\\b(es-(ger?|al(e|em)?)|(ger?|al(e|em)?)-es)\\b", "MULTi SPANiSH GERMANx"] # misspelled to accomodate negative lookback, corrected later
- name: re_replace
args: ["[ -](?i)vas[ -]", " Basque "]
args: ["(?i)\\btriaudio\\b", "MULTi SPANiSH"]
- name: re_replace
args: ["[ -](?i)fr[ae][ -]", " French "]
args: ["(?i)\\b(espa[ñn]ol|castellano|esp)\\b", "SPANiSH"]
- name: re_replace
args: ["[ -](?i)jap[ -]", " Japanese "]
args: ["(?i)\\b(ingl[ée]s|[ei]ng)\\b", "ENGLiSH"]
- name: re_replace
args: ["[ -](?i)ita[ -]", " Italian "]
args: ["(?i)\\bcat\\b", "CATALAN"]
- name: re_replace
args: ["[ -](?i)rus[ -]", " Russian "]
args: ["(?i)\\bfr[ae]n?\\b", "FRENCHx"] # misspelled to accomodate negative lookback, corrected later
- name: re_replace
args: ["[ -](?i)ger[ -]", " German "]
args: ["(?i)\\b(jap|jp)\\b", "JAPANES"] # misspelled to accomodate negative lookback, corrected later
- name: re_replace
args: ["(?i)(triaudio)", "MULTi SPANiSH English"]
args: ["(?i)\\bita\\b", "iTALiAN"]
- name: re_replace
args: ["(?i)(dual)", " MULTi SPANiSH "]
args: ["(?i)\\brus?\\b", "RUSSiAN"]
- name: re_replace
args: ["(?i)(es-en)", " MULTi SPANiSH "]
args: ["(?i)\\b(ger?|alem)\\b", "GERMANx"] # misspelled to accomodate negative lookback, corrected later
- name: re_replace
args: ["(?i)(en-es)", " MULTi SPANiSH "]
args: ["(\\s|\\.)+", "$1"]
# add MULTi SPANiSH if not preceded by another language or followed by SPANiSH
- name: re_replace
args: ["\\.+", "."] # More than 1 dot -> .
args: ["(?<!(SPANiSH|ENGLiSH|CATALAN|FRENCHx|JAPANES|iTALiAN|RUSSiAN|GERMANx)[\\s.])\\b(ENGLiSH|CATALAN|FRENCHx|JAPANES|iTALiAN|RUSSiAN|GERMANx)\\b(?![\\s.]SPANiSH)", "MULTi SPANiSH $2"]
# correct purposefully misspelled languages
- name: re_replace
args: ["^\\.", ""] # Delete first dot
args: ["\\bFRENCHx\\b", "FRENCH"]
- name: re_replace
args: ["\\s+", " "] # More than 1 space to 1 space
args: ["\\bJAPANES\\b", "JAPANESE"]
- name: re_replace
args: ["\\bGERMANx\\b", "GERMAN"]
title_spanish:
text: "{{ .Result.title_phase1 }}"
filters:
- name: regexp
args: "(?i)spanish"
title:
text: "{{ .Result.title_phase1 }}"
filters:
# append SPANiSH if title does not contain the word 'spanish'
- name: append
args: "{{ if .Result.title_spanish }}{{ else }} SPANiSH{{ end }}"
details:
selector: details_link
download:

View File

@ -7,7 +7,7 @@ type: semi-private
encoding: windows-1251
followredirect: true
links:
- https://27april.rudub.online/
- https://28april.rudub.online/
legacylinks:
- https://rudub.online/
- https://06april.rudub.online/
@ -32,6 +32,7 @@ legacylinks:
- https://25pril.rudub.online/ # typo
- https://25april.rudub.online/
- https://26april.rudub.online/
- https://27april.rudub.online/
caps:
categorymappings:

View File

@ -6,6 +6,8 @@ language: en-US
type: public
encoding: UTF-8
requestDelay: 2
certificates:
- 1fe945ddaa652aa3c5fd894a569873aa62cc6937 # expired 12 Apr 2024
links:
- https://www.torlock.com/
- https://www.torlock2.com/

View File

@ -7,7 +7,7 @@ type: public
encoding: UTF-8
# to fetch current domain use https://www.protege-torrent.com/Torrent911 and https://www.protege-torrent.com/T911 and https://www.protege-torrent.com/Oxtorrent
links:
- https://www.oxtorrent.wtf/
- https://www.oxtorrent.tf/
- https://oxtorrent.proxyninja.org/ # proxy for oxtorrent
legacylinks:
- https://www.protege-liens.com/
@ -35,6 +35,7 @@ legacylinks:
- https://www.t911.pw/ # now on poster layout
- https://www.oxtorrent.nl/
- https://www.oxtorrent.sbs/
- https://www.oxtorrent.wtf/
caps:
categorymappings:

View File

@ -170,10 +170,10 @@ search:
selector: a[href^="details.php?id="]
attribute: href
download:
selector: a[href^="download.php?id="]
selector: a[href^="download.php?id="], a[href^="magnet:?xt="]
attribute: href
size:
selector: a[href^="download.php?id="]
selector: a[href^="download.php?id="], a[href^="magnet:?xt="]
seeders:
selector: td:nth-last-child(3)
filters:

View File

@ -8,7 +8,7 @@ encoding: UTF-8
followredirect: true
requestDelay: 2
links:
- https://www3.yggtorrent.qa/
- https://www3.yggtorrent.cool/
legacylinks:
- https://ww3.yggtorrent.si/
- https://yggtorrent.si/
@ -25,6 +25,7 @@ legacylinks:
- https://www6.yggtorrent.lol/
- https://www3.yggtorrent.do/
- https://www3.yggtorrent.wtf/
- https://www3.yggtorrent.qa/
caps:
# dont forget to update the search fields category case block

View File

@ -8,7 +8,7 @@ encoding: UTF-8
followredirect: true
requestDelay: 2
links:
- https://www3.yggtorrent.qa/
- https://www3.yggtorrent.cool/
legacylinks:
- https://ww3.yggtorrent.si/
- https://yggtorrent.si/
@ -25,6 +25,7 @@ legacylinks:
- https://www6.yggtorrent.lol/
- https://www3.yggtorrent.do/
- https://www3.yggtorrent.wtf/
- https://www3.yggtorrent.qa/
caps:
# dont forget to update the search fields category case block

View File

@ -0,0 +1,9 @@
using Newtonsoft.Json.Serialization;
namespace Jackett.Common.Helpers
{
public class LowerCaseNamingStrategy : NamingStrategy
{
protected override string ResolvePropertyName(string name) => name.ToLowerInvariant();
}
}

View File

@ -0,0 +1,71 @@
using System.Linq;
using System.Xml.Linq;
using Jackett.Common.Extensions;
using Newtonsoft.Json.Linq;
namespace Jackett.Common.Helpers
{
public static class XmlToJsonConverter
{
/// <summary>
/// Converts an XML element to a JSON object.
/// Attributes are stored in an @attributes object.
/// </summary>
public static JToken XmlToJson(XElement element)
{
var obj = new JObject();
// Build @attributes object from element attributes
if (element.Attributes().Any())
{
var attributes = element.Attributes()
.ToDictionary(
attribute => GetName(attribute.Name, attribute.Parent?.Document),
attribute => (JToken)attribute.Value
);
obj.Add("@attributes", JObject.FromObject(attributes));
}
foreach (var childElement in element.Elements())
{
var childObj = XmlToJson(childElement);
var childName = GetName(childElement.Name, element.Document);
// If the child name already exists, convert it to an array
if (obj.ContainsKey(childName))
{
if (obj[childName] is JArray existingArray)
{
existingArray.Add(childObj);
}
else
{
obj[childName] = new JArray(obj[childName]!, childObj);
}
}
else
{
obj.Add(childName, childObj);
}
}
if (!element.Elements().Any() && !element.Value.IsNullOrWhiteSpace())
{
return element.Value;
}
return obj;
}
private static string GetName(XName name, XDocument document)
{
if (document == null)
{
return name.LocalName;
}
var prefix = document.Root?.GetPrefixOfNamespace(name.Namespace);
return prefix != null ? $"{prefix}:{name.LocalName}" : name.LocalName;
}
}
}

View File

@ -10,6 +10,7 @@ namespace Jackett.Common.Models.DTO
public string imdbid { get; set; }
public string ep { get; set; }
public string t { get; set; }
public string o { get; set; }
public string extended { get; set; }
public string limit { get; set; }
public string offset { get; set; }

View File

@ -5,6 +5,8 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml.Linq;
using Jackett.Common.Helpers;
using Newtonsoft.Json;
namespace Jackett.Common.Models
{
@ -52,7 +54,7 @@ namespace Jackett.Common.Models
return new XElement(_TorznabNs + "attr", new XAttribute("name", name), new XAttribute("value", value));
}
public string ToXml(Uri selfAtom)
private XDocument GetXDocument(Uri selfAtom)
{
// IMPORTANT: We can't use Uri.ToString(), because it generates URLs without URL encode (links with unicode
// characters are broken). We must use Uri.AbsoluteUri instead that handles encoding correctly
@ -127,7 +129,19 @@ namespace Jackett.Common.Models
)
);
return xdoc;
}
public string ToXml(Uri selfAtom)
{
var xdoc = GetXDocument(selfAtom);
return xdoc.Declaration + Environment.NewLine + xdoc;
}
public string ToJson(Uri selfAtom, JsonSerializerSettings serializerSettings = null)
{
var jsonObject = XmlToJsonConverter.XmlToJson(GetXDocument(selfAtom).Root);
return JsonConvert.SerializeObject(jsonObject, serializerSettings);
}
}
}

View File

@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Jackett.Common.Helpers;
using Newtonsoft.Json;
namespace Jackett.Common.Models
{
@ -352,6 +354,12 @@ namespace Jackett.Common.Models
public string ToXml() =>
GetXDocument().Declaration + Environment.NewLine + GetXDocument();
public string ToJson(JsonSerializerSettings serializerSettings = null)
{
var jsonObject = XmlToJsonConverter.XmlToJson(GetXDocument().Root);
return JsonConvert.SerializeObject(jsonObject, serializerSettings);
}
public static TorznabCapabilities Concat(TorznabCapabilities lhs, TorznabCapabilities rhs)
{
lhs.SearchAvailable = lhs.SearchAvailable || rhs.SearchAvailable;

View File

@ -48,6 +48,8 @@ namespace Jackett.Common.Models
public bool IsTest { get; set; }
public bool IsJson { get; set; }
public string ImdbIDShort => ImdbID?.TrimStart('t');
protected string[] QueryStringParts;

View File

@ -8,6 +8,7 @@ using System.Threading.Tasks;
using System.Xml.Linq;
using Jackett.Common;
using Jackett.Common.Exceptions;
using Jackett.Common.Helpers;
using Jackett.Common.Indexers;
using Jackett.Common.Indexers.Meta;
using Jackett.Common.Models;
@ -20,6 +21,9 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using NLog;
namespace Jackett.Server.Controllers
@ -175,6 +179,17 @@ namespace Jackett.Server.Controllers
private readonly IServerService serverService;
private readonly ICacheService cacheService;
private readonly Common.Models.Config.ServerConfig serverConfig;
private static readonly JsonSerializerSettings _JsonSerializerSettings = new JsonSerializerSettings
{
Converters = new List<JsonConverter>
{
new StringEnumConverter
{
NamingStrategy = new LowerCaseNamingStrategy()
}
},
NullValueHandling = NullValueHandling.Ignore
};
public ResultsController(IIndexerManagerService indexerManagerService, IServerService ss, ICacheService c, Logger logger, Common.Models.Config.ServerConfig sConfig)
{
@ -339,9 +354,16 @@ namespace Jackett.Server.Controllers
[HttpGet]
public async Task<IActionResult> Torznab([FromQuery] TorznabRequest request)
{
if (string.Equals(request.o, "json", StringComparison.InvariantCultureIgnoreCase))
{
CurrentQuery.IsJson = true;
}
if (string.Equals(CurrentQuery.QueryType, "caps", StringComparison.InvariantCultureIgnoreCase))
{
return Content(CurrentIndexer.TorznabCaps.ToXml(), "application/rss+xml", Encoding.UTF8);
return CurrentQuery.IsJson ?
Content(CurrentIndexer.TorznabCaps.ToJson(_JsonSerializerSettings), "application/json", Encoding.UTF8) :
Content(CurrentIndexer.TorznabCaps.ToXml(), "application/rss+xml", Encoding.UTF8);
}
// indexers - returns a list of all included indexers (meta indexers only)
@ -350,7 +372,7 @@ namespace Jackett.Server.Controllers
if (!(CurrentIndexer is BaseMetaIndexer)) // shouldn't be needed because CanHandleQuery should return false
{
logger.Warn($"A search request with t=indexers from {Request.HttpContext.Connection.RemoteIpAddress} was made but the indexer {CurrentIndexer.Name} isn't a meta indexer.");
return GetErrorXML(203, "Function Not Available: this isn't a meta indexer");
return GetError(203, "Function Not Available: this isn't a meta indexer");
}
var CurrentBaseMetaIndexer = (BaseMetaIndexer)CurrentIndexer;
var indexers = CurrentBaseMetaIndexer.Indexers;
@ -376,6 +398,11 @@ namespace Jackett.Server.Controllers
)
);
if (CurrentQuery.IsJson)
{
return Json(XmlToJsonConverter.XmlToJson(xdoc.Root), _JsonSerializerSettings);
}
return Content(xdoc.Declaration.ToString() + Environment.NewLine + xdoc.ToString(), "application/xml", Encoding.UTF8);
}
@ -395,19 +422,19 @@ namespace Jackett.Server.Controllers
if (CurrentQuery.ImdbID == null)
{
logger.Warn($"A search request from {Request.HttpContext.Connection.RemoteIpAddress} was made with an invalid imdbid.");
return GetErrorXML(201, "Incorrect parameter: invalid imdbid format");
return GetError(201, "Incorrect parameter: invalid imdbid format");
}
if (CurrentQuery.IsMovieSearch && !CurrentIndexer.TorznabCaps.MovieSearchImdbAvailable)
{
logger.Warn($"A search request with imdbid from {Request.HttpContext.Connection.RemoteIpAddress} was made but the indexer {CurrentIndexer.Name} doesn't support it.");
return GetErrorXML(203, "Function Not Available: imdbid is not supported for movie search by this indexer");
return GetError(203, "Function Not Available: imdbid is not supported for movie search by this indexer");
}
if (CurrentQuery.IsTVSearch && !CurrentIndexer.TorznabCaps.TvSearchImdbAvailable)
{
logger.Warn($"A search request with imdbid from {Request.HttpContext.Connection.RemoteIpAddress} was made but the indexer {CurrentIndexer.Name} doesn't support it.");
return GetErrorXML(203, "Function Not Available: imdbid is not supported for TV search by this indexer");
return GetError(203, "Function Not Available: imdbid is not supported for TV search by this indexer");
}
}
@ -416,13 +443,13 @@ namespace Jackett.Server.Controllers
if (CurrentQuery.IsMovieSearch && !CurrentIndexer.TorznabCaps.MovieSearchTmdbAvailable)
{
logger.Warn($"A search request with tmdbid from {Request.HttpContext.Connection.RemoteIpAddress} was made but the indexer {CurrentIndexer.Name} doesn't support it.");
return GetErrorXML(203, "Function Not Available: tmdbid is not supported for movie search by this indexer");
return GetError(203, "Function Not Available: tmdbid is not supported for movie search by this indexer");
}
if (CurrentQuery.IsTVSearch && !CurrentIndexer.TorznabCaps.TvSearchTmdbAvailable)
{
logger.Warn($"A search request with tmdbid from {Request.HttpContext.Connection.RemoteIpAddress} was made but the indexer {CurrentIndexer.Name} doesn't support it.");
return GetErrorXML(203, "Function Not Available: tmdbid is not supported for TV search by this indexer");
return GetError(203, "Function Not Available: tmdbid is not supported for TV search by this indexer");
}
}
@ -431,7 +458,7 @@ namespace Jackett.Server.Controllers
if (CurrentQuery.IsTVSearch && !CurrentIndexer.TorznabCaps.TvSearchAvailable)
{
logger.Warn($"A search request with tvdbid from {Request.HttpContext.Connection.RemoteIpAddress} was made but the indexer {CurrentIndexer.Name} doesn't support it.");
return GetErrorXML(203, "Function Not Available: tvdbid is not supported for movie search by this indexer");
return GetError(203, "Function Not Available: tvdbid is not supported for movie search by this indexer");
}
}
@ -465,7 +492,14 @@ namespace Jackett.Server.Controllers
else
logger.Info($"Torznab search in {CurrentIndexer.Name} for {CurrentQuery.GetQueryString()} => Found {result.Releases.Count()} releases{cacheStr} [{stopwatch.ElapsedMilliseconds:0}ms]");
var xml = resultPage.ToXml(new Uri(serverUrl));
var serverUri = new Uri(serverUrl);
if (CurrentQuery.IsJson)
{
var json = resultPage.ToJson(serverUri);
return Content(json, "application/json", Encoding.UTF8);
}
var xml = resultPage.ToXml(serverUri);
// Force the return as XML
return Content(xml, "application/rss+xml", Encoding.UTF8);
@ -485,25 +519,29 @@ namespace Jackett.Server.Controllers
}
}
return GetErrorXML(900, ex.Message, StatusCodes.Status429TooManyRequests);
return GetError(900, ex.Message, StatusCodes.Status429TooManyRequests);
}
catch (Exception e)
{
logger.Error(e);
return GetErrorXML(900, e.ToString());
return GetError(900, e.ToString());
}
}
[Route("[action]/{ignored?}")]
public IActionResult GetErrorXML(int code, string description, int statusCode = StatusCodes.Status400BadRequest)
public IActionResult GetError(int code, string description, int statusCode = StatusCodes.Status400BadRequest)
{
var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse("application/xml");
var mediaTypeHeaderValue = CurrentQuery.IsJson ?
MediaTypeHeaderValue.Parse("application/json") :
MediaTypeHeaderValue.Parse("application/xml");
mediaTypeHeaderValue.Encoding = Encoding.UTF8;
return new ContentResult
{
StatusCode = statusCode,
Content = CreateErrorXML(code, description),
Content = CurrentQuery.IsJson ?
CreateErrorJson(code, description) :
CreateErrorXML(code, description),
ContentType = mediaTypeHeaderValue.ToString()
};
}
@ -520,6 +558,19 @@ namespace Jackett.Server.Controllers
return xdoc.Declaration + Environment.NewLine + xdoc;
}
public static string CreateErrorJson(int code, string description)
{
return new JObject
{
{ "error", new JObject
{
{ "code", code },
{ "description", description }
}
}
}.ToString();
}
public static IActionResult GetErrorActionResult(RouteData routeData, HttpStatusCode status, int torznabCode, string description)
{
var isTorznab = routeData.Values["action"].ToString().Equals("torznab", StringComparison.OrdinalIgnoreCase);

View File

@ -0,0 +1,83 @@
using System.Xml.Linq;
using Jackett.Common.Helpers;
using Newtonsoft.Json;
using NUnit.Framework;
namespace Jackett.Test.Common.Helpers
{
[TestFixture]
public class XmlToJsonConverterTests
{
[Test]
public void XmlToJsonConverter_XmlToJson_Empty()
{
var element = new XElement("test");
var json = XmlToJsonConverter.XmlToJson(element);
Assert.AreEqual("{}", json.ToString(Formatting.None));
}
[Test]
public void XmlToJsonConverter_XmlToJson_WithAttributes()
{
var element = new XElement("test", new XAttribute("attr1", "value1"), new XAttribute("attr2", "value2"));
var json = XmlToJsonConverter.XmlToJson(element);
Assert.AreEqual("{\"@attributes\":{\"attr1\":\"value1\",\"attr2\":\"value2\"}}", json.ToString(Formatting.None));
}
[Test]
public void XmlToJsonConverter_XmlToJson_WithChildElement()
{
var element = new XElement("test", new XElement("child"));
var json = XmlToJsonConverter.XmlToJson(element);
Assert.AreEqual("{\"child\":{}}", json.ToString(Formatting.None));
}
[Test]
public void XmlToJsonConverter_XmlToJson_WithChildElement_WithAttributes()
{
var element = new XElement("test", new XElement("child", new XAttribute("attr1", "value1"), new XAttribute("attr2", "value2")));
var json = XmlToJsonConverter.XmlToJson(element);
Assert.AreEqual("{\"child\":{\"@attributes\":{\"attr1\":\"value1\",\"attr2\":\"value2\"}}}", json.ToString(Formatting.None));
}
[Test]
public void XmlToJsonConverter_XmlToJson_WithChildElement_WithText()
{
var element = new XElement("test", new XElement("child", new XElement("subchild", "text")));
var json = XmlToJsonConverter.XmlToJson(element);
Assert.AreEqual("{\"child\":{\"subchild\":\"text\"}}", json.ToString(Formatting.None));
}
[Test]
public void XmlToJsonConverter_XmlToJson_WithChildElementArray()
{
var element = new XElement("test", new XElement("child"), new XElement("child"));
var json = XmlToJsonConverter.XmlToJson(element);
Assert.AreEqual("{\"child\":[{},{}]}", json.ToString(Formatting.None));
}
[Test]
public void XmlToJsonConverter_XmlToJson_WithChildElementArray_WithText()
{
var element = new XElement("test", new XElement("child", "text1"), new XElement("child", "text2"));
var json = XmlToJsonConverter.XmlToJson(element);
Assert.AreEqual("{\"child\":[\"text1\",\"text2\"]}", json.ToString(Formatting.None));
}
[Test]
public void XmlToJsonConverter_XmlToJson_HandlesNamespaces()
{
var document = new XDocument(
new XElement("root",
new XAttribute(XNamespace.Xmlns + "ns1", "http://example.com/ns1"),
new XAttribute(XNamespace.Xmlns + "ns2", "http://example.com/ns2"),
new XElement("{http://example.com/ns1}child1"),
new XElement("{http://example.com/ns2}child2")
)
);
var json = XmlToJsonConverter.XmlToJson(document.Root);
Assert.AreEqual("{\"@attributes\":{\"xmlns:ns1\":\"http://example.com/ns1\",\"xmlns:ns2\":\"http://example.com/ns2\"},\"ns1:child1\":{},\"ns2:child2\":{}}", json.ToString(Formatting.None));
}
}
}