-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
ResultPage.cs
133 lines (124 loc) · 7.5 KB
/
ResultPage.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml.Linq;
namespace Jackett.Common.Models
{
public class ResultPage
{
private static readonly XNamespace _AtomNs = "http://www.w3.org/2005/Atom";
private static readonly XNamespace _TorznabNs = "http://torznab.com/schemas/2015/feed";
// filters control characters but allows only properly-formed surrogate sequences
// https://stackoverflow.com/a/961504
private static readonly Regex _InvalidXmlChars = new Regex(
@"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]",
RegexOptions.Compiled);
private ChannelInfo ChannelInfo { get; }
public IEnumerable<ReleaseInfo> Releases { get; set; }
public ResultPage(ChannelInfo channelInfo)
{
ChannelInfo = channelInfo;
Releases = new List<ReleaseInfo>();
}
/// <summary>
/// removes any unusual unicode characters that can't be encoded into XML (eg 0x1A)
/// </summary>
private static string RemoveInvalidXMLChars(string text)
{
if (text == null)
return null;
return _InvalidXmlChars.Replace(text, "");
}
private static string XmlDateFormat(DateTime dt)
{
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
//Sat, 14 Mar 2015 17:10:42 -0400
return $"{dt:ddd, dd MMM yyyy HH:mm:ss} " + $"{dt:zzz}".Replace(":", "");
}
private static XElement GetTorznabElement(string name, object value)
{
if (value == null)
return null;
return new XElement(_TorznabNs + "attr", new XAttribute("name", name), new XAttribute("value", value));
}
public string ToXml(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
var xdoc = new XDocument(
new XDeclaration("1.0", "UTF-8", null),
new XElement("rss",
new XAttribute("version", "2.0"),
new XAttribute(XNamespace.Xmlns + "atom", _AtomNs.NamespaceName),
new XAttribute(XNamespace.Xmlns + "torznab", _TorznabNs.NamespaceName),
new XElement("channel",
new XElement(_AtomNs + "link",
new XAttribute("href", selfAtom.AbsoluteUri),
new XAttribute("rel", "self"),
new XAttribute("type", "application/rss+xml")
),
new XElement("title", ChannelInfo.Title),
new XElement("description", ChannelInfo.Description),
new XElement("link", ChannelInfo.Link.AbsoluteUri),
new XElement("language", ChannelInfo.Language),
new XElement("category", ChannelInfo.Category),
from r in Releases
select new XElement("item",
new XElement("title", RemoveInvalidXMLChars(r.Title)),
new XElement("guid", r.Guid.AbsoluteUri), // GUID and (Link or Magnet) are mandatory
new XElement("jackettindexer", new XAttribute("id", r.Origin.Id), r.Origin.Name),
new XElement("type", r.Origin.Type),
r.Details == null ? null : new XElement("comments", r.Details.AbsoluteUri),
r.PublishDate == DateTime.MinValue ? new XElement("pubDate", XmlDateFormat(DateTime.Now)) : new XElement("pubDate", XmlDateFormat(r.PublishDate)),
r.Size == null ? null : new XElement("size", r.Size),
r.Files == null ? null : new XElement("files", r.Files),
r.Grabs == null ? null : new XElement("grabs", r.Grabs),
new XElement("description", RemoveInvalidXMLChars(r.Description)),
new XElement("link", r.Link?.AbsoluteUri ?? r.MagnetUri.AbsoluteUri),
r.Category == null ? null : from c in r.Category select new XElement("category", c),
new XElement(
"enclosure",
new XAttribute("url", r.Link?.AbsoluteUri ?? r.MagnetUri.AbsoluteUri),
r.Size == null ? null : new XAttribute("length", r.Size),
new XAttribute("type", "application/x-bittorrent")
),
r.Category == null ? null : from c in r.Category select GetTorznabElement("category", c),
GetTorznabElement("rageid", r.RageID),
GetTorznabElement("tvdbid", r.TVDBId),
GetTorznabElement("imdb", r.Imdb?.ToString("D7")),
GetTorznabElement("imdbid", r.Imdb != null ? "tt" + r.Imdb?.ToString("D7") : null),
GetTorznabElement("tmdbid", r.TMDb),
GetTorznabElement("tvmazeid", r.TVMazeId),
GetTorznabElement("traktid", r.TraktId),
GetTorznabElement("doubanid", r.DoubanId),
r.Genres == null ? null : GetTorznabElement("genre", string.Join(", ", r.Genres)),
r.Languages == null ? null : from c in r.Languages select GetTorznabElement("language", c),
r.Subs == null ? null : from c in r.Subs select GetTorznabElement("subs", c),
GetTorznabElement("year", r.Year),
GetTorznabElement("author", RemoveInvalidXMLChars(r.Author)),
GetTorznabElement("booktitle", RemoveInvalidXMLChars(r.BookTitle)),
GetTorznabElement("publisher", RemoveInvalidXMLChars(r.Publisher)),
GetTorznabElement("artist", RemoveInvalidXMLChars(r.Artist)),
GetTorznabElement("album", RemoveInvalidXMLChars(r.Album)),
GetTorznabElement("label", RemoveInvalidXMLChars(r.Label)),
GetTorznabElement("track", RemoveInvalidXMLChars(r.Track)),
GetTorznabElement("seeders", r.Seeders),
GetTorznabElement("peers", r.Peers),
GetTorznabElement("coverurl", r.Poster?.AbsoluteUri),
GetTorznabElement("infohash", RemoveInvalidXMLChars(r.InfoHash)),
GetTorznabElement("magneturl", r.MagnetUri?.AbsoluteUri),
GetTorznabElement("minimumratio", r.MinimumRatio),
GetTorznabElement("minimumseedtime", r.MinimumSeedTime),
GetTorznabElement("downloadvolumefactor", r.DownloadVolumeFactor),
GetTorznabElement("uploadvolumefactor", r.UploadVolumeFactor)
)
)
)
);
return xdoc.Declaration + Environment.NewLine + xdoc;
}
}
}