.oO SearXNG Developer Documentation Oo.
Loading...
Searching...
No Matches
torznab.py
Go to the documentation of this file.
1# SPDX-License-Identifier: AGPL-3.0-or-later
2"""Torznab_ is an API specification that provides a standardized way to query
3torrent site for content. It is used by a number of torrent applications,
4including Prowlarr_ and Jackett_.
5
6Using this engine together with Prowlarr_ or Jackett_ allows you to search
7a huge number of torrent sites which are not directly supported.
8
9Configuration
10=============
11
12The engine has the following settings:
13
14``base_url``:
15 Torznab endpoint URL.
16
17``api_key``:
18 The API key to use for authentication.
19
20``torznab_categories``:
21 The categories to use for searching. This is a list of category IDs. See
22 Prowlarr-categories_ or Jackett-categories_ for more information.
23
24``show_torrent_files``:
25 Whether to show the torrent file in the search results. Be careful as using
26 this with Prowlarr_ or Jackett_ leaks the API key. This should be used only
27 if you are querying a Torznab endpoint without authentication or if the
28 instance is private. Be aware that private trackers may ban you if you share
29 the torrent file. Defaults to ``false``.
30
31``show_magnet_links``:
32 Whether to show the magnet link in the search results. Be aware that private
33 trackers may ban you if you share the magnet link. Defaults to ``true``.
34
35.. _Torznab:
36 https://torznab.github.io/spec-1.3-draft/index.html
37.. _Prowlarr:
38 https://github.com/Prowlarr/Prowlarr
39.. _Jackett:
40 https://github.com/Jackett/Jackett
41.. _Prowlarr-categories:
42 https://wiki.servarr.com/en/prowlarr/cardigann-yml-definition#categories
43.. _Jackett-categories:
44 https://github.com/Jackett/Jackett/wiki/Jackett-Categories
45
46Implementations
47===============
48
49"""
50
51import typing as t
52from datetime import datetime
53from urllib.parse import quote
54from lxml import etree # type: ignore
55
56from searx.exceptions import SearxEngineAPIException
57from searx.utils import humanize_bytes
58
59if t.TYPE_CHECKING:
60 from searx.extended_types import SXNG_Response
61
62
63# engine settings
64about: dict[str, t.Any] = {
65 "website": None,
66 "wikidata_id": None,
67 "official_api_documentation": "https://torznab.github.io/spec-1.3-draft",
68 "use_official_api": True,
69 "require_api_key": False,
70 "results": 'XML',
71}
72categories: list[str] = ['files']
73paging: bool = False
74time_range_support: bool = False
75
76# defined in settings.yml
77# example (Jackett): "http://localhost:9117/api/v2.0/indexers/all/results/torznab"
78base_url: str = ''
79api_key: str = ''
80# https://newznab.readthedocs.io/en/latest/misc/api/#predefined-categories
81torznab_categories: list[str] = []
82show_torrent_files: bool = False
83show_magnet_links: bool = True
84
85
86def init(engine_settings=None): # pylint: disable=unused-argument
87 """Initialize the engine."""
88 if len(base_url) < 1:
89 raise ValueError('missing torznab base_url')
90
91
92def request(query: str, params: dict[str, t.Any]) -> dict[str, t.Any]:
93 """Build the request params."""
94 search_url: str = base_url + '?t=search&q={search_query}'
95
96 if len(api_key) > 0:
97 search_url += '&apikey={api_key}'
98 if len(torznab_categories) > 0:
99 search_url += '&cat={torznab_categories}'
100
101 params['url'] = search_url.format(
102 search_query=quote(query), api_key=api_key, torznab_categories=",".join([str(x) for x in torznab_categories])
103 )
104
105 return params
106
107
108def response(resp: "SXNG_Response") -> list[dict[str, t.Any]]:
109 """Parse the XML response and return a list of results."""
110 results = []
111 search_results = etree.XML(resp.content)
112
113 # handle errors: https://newznab.readthedocs.io/en/latest/misc/api/#newznab-error-codes
114 if search_results.tag == "error":
115 raise SearxEngineAPIException(search_results.get("description"))
116
117 channel: etree.Element = search_results[0]
118
119 item: etree.Element
120 for item in channel.iterfind('item'):
121 result: dict[str, t.Any] = build_result(item)
122 results.append(result)
123
124 return results
125
126
127def build_result(item: etree.Element) -> dict[str, t.Any]:
128 """Build a result from a XML item."""
129
130 # extract attributes from XML
131 # see https://torznab.github.io/spec-1.3-draft/torznab/Specification-v1.3.html#predefined-attributes
132 enclosure: etree.Element | None = item.find('enclosure')
133 enclosure_url: str | None = None
134 if enclosure is not None:
135 enclosure_url = enclosure.get('url')
136
137 filesize = get_attribute(item, 'size')
138 if not filesize and enclosure:
139 filesize = enclosure.get('length')
140
141 guid = get_attribute(item, 'guid')
142 comments = get_attribute(item, 'comments')
143 pubDate = get_attribute(item, 'pubDate')
144 seeders = get_torznab_attribute(item, 'seeders')
145 leechers = get_torznab_attribute(item, 'leechers')
146 peers = get_torznab_attribute(item, 'peers')
147
148 # map attributes to SearXNG result
149 result: dict[str, t.Any] = {
150 'template': 'torrent.html',
151 'title': get_attribute(item, 'title'),
152 'filesize': humanize_bytes(int(filesize)) if filesize else None,
153 'files': get_attribute(item, 'files'),
154 'seed': seeders,
155 'leech': _map_leechers(leechers, seeders, peers),
156 'url': _map_result_url(guid, comments),
157 'publishedDate': _map_published_date(pubDate),
158 'torrentfile': None,
159 'magnetlink': None,
160 }
161
162 link = get_attribute(item, 'link')
163 if show_torrent_files:
164 result['torrentfile'] = _map_torrent_file(link, enclosure_url)
165 if show_magnet_links:
166 magneturl = get_torznab_attribute(item, 'magneturl')
167 result['magnetlink'] = _map_magnet_link(magneturl, guid, enclosure_url, link)
168 return result
169
170
171def _map_result_url(guid: str | None, comments: str | None) -> str | None:
172 if guid and guid.startswith('http'):
173 return guid
174 if comments and comments.startswith('http'):
175 return comments
176 return None
177
178
179def _map_leechers(leechers: str | None, seeders: str | None, peers: str | None) -> str | None:
180 if leechers:
181 return leechers
182 if seeders and peers:
183 return str(int(peers) - int(seeders))
184 return None
185
186
187def _map_published_date(pubDate: str | None) -> datetime | None:
188 if pubDate is not None:
189 try:
190 return datetime.strptime(pubDate, '%a, %d %b %Y %H:%M:%S %z')
191 except (ValueError, TypeError) as e:
192 logger.debug("ignore exception (publishedDate): %s", e)
193 return None
194
195
196def _map_torrent_file(link: str | None, enclosure_url: str | None) -> str | None:
197 if link and link.startswith('http'):
198 return link
199 if enclosure_url and enclosure_url.startswith('http'):
200 return enclosure_url
201 return None
202
203
205 magneturl: str | None,
206 guid: str | None,
207 enclosure_url: str | None,
208 link: str | None,
209) -> str | None:
210 if magneturl and magneturl.startswith('magnet'):
211 return magneturl
212 if guid and guid.startswith('magnet'):
213 return guid
214 if enclosure_url and enclosure_url.startswith('magnet'):
215 return enclosure_url
216 if link and link.startswith('magnet'):
217 return link
218 return None
219
220
221def get_attribute(item: etree.Element, property_name: str) -> str | None:
222 """Get attribute from item."""
223 property_element: etree.Element | None = item.find(property_name)
224 if property_element is not None:
225 return property_element.text
226 return None
227
228
229def get_torznab_attribute(item: etree.Element, attribute_name: str) -> str | None:
230 """Get torznab special attribute from item."""
231 element: etree.Element | None = item.find(
232 './/torznab:attr[@name="{attribute_name}"]'.format(attribute_name=attribute_name),
233 {'torznab': 'http://torznab.com/schemas/2015/feed'},
234 )
235 if element is not None:
236 return element.get("value")
237 return None
datetime|None _map_published_date(str|None pubDate)
Definition torznab.py:187
str|None _map_result_url(str|None guid, str|None comments)
Definition torznab.py:171
str|None _map_torrent_file(str|None link, str|None enclosure_url)
Definition torznab.py:196
str|None get_torznab_attribute(etree.Element item, str attribute_name)
Definition torznab.py:229
str|None _map_leechers(str|None leechers, str|None seeders, str|None peers)
Definition torznab.py:179
init(engine_settings=None)
Definition torznab.py:86
list[dict[str, t.Any]] response("SXNG_Response" resp)
Definition torznab.py:108
dict[str, t.Any] build_result(etree.Element item)
Definition torznab.py:127
str|None _map_magnet_link(str|None magneturl, str|None guid, str|None enclosure_url, str|None link)
Definition torznab.py:209
str|None get_attribute(etree.Element item, str property_name)
Definition torznab.py:221
dict[str, t.Any] request(str query, dict[str, t.Any] params)
Definition torznab.py:92