2021-10-07 00:34:42 +00:00
|
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
2021-10-07 07:35:50 +00:00
|
|
|
# lint: pylint
|
2021-10-07 00:34:42 +00:00
|
|
|
"""Torznab WebAPI
|
|
|
|
|
|
|
|
A engine that implements the `torznab WebAPI`_.
|
|
|
|
|
|
|
|
.. _torznab WebAPI: https://torznab.github.io/spec-1.3-draft/torznab
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2021-10-07 07:35:50 +00:00
|
|
|
from datetime import datetime
|
2021-10-07 00:34:42 +00:00
|
|
|
from urllib.parse import quote
|
|
|
|
from lxml import etree
|
|
|
|
|
|
|
|
from searx.exceptions import SearxEngineAPIException
|
|
|
|
|
|
|
|
# about
|
|
|
|
about = {
|
|
|
|
"website": None,
|
|
|
|
"wikidata_id": None,
|
2021-10-07 07:35:50 +00:00
|
|
|
"official_api_documentation": "https://torznab.github.io/spec-1.3-draft",
|
2021-10-07 00:34:42 +00:00
|
|
|
"use_official_api": True,
|
|
|
|
"require_api_key": False,
|
|
|
|
"results": 'XML',
|
|
|
|
}
|
|
|
|
|
|
|
|
categories = ['files']
|
|
|
|
paging = False
|
|
|
|
time_range_support = False
|
|
|
|
|
|
|
|
# defined in settings.yml
|
|
|
|
# example (Jackett): "http://localhost:9117/api/v2.0/indexers/all/results/torznab"
|
|
|
|
base_url = ''
|
|
|
|
api_key = ''
|
|
|
|
# https://newznab.readthedocs.io/en/latest/misc/api/#predefined-categories
|
|
|
|
torznab_categories = []
|
|
|
|
|
|
|
|
|
|
|
|
def request(query, params):
|
|
|
|
if len(base_url) < 1:
|
|
|
|
raise SearxEngineAPIException('missing torznab base_url')
|
|
|
|
|
|
|
|
search_url = base_url + '?t=search&q={search_query}'
|
|
|
|
if len(api_key) > 0:
|
|
|
|
search_url += '&apikey={api_key}'
|
|
|
|
if len(torznab_categories) > 0:
|
|
|
|
search_url += '&cat={torznab_categories}'
|
|
|
|
|
|
|
|
params['url'] = search_url.format(
|
|
|
|
search_query=quote(query),
|
|
|
|
api_key=api_key,
|
|
|
|
torznab_categories=",".join(torznab_categories)
|
|
|
|
)
|
|
|
|
|
|
|
|
return params
|
|
|
|
|
|
|
|
|
|
|
|
def response(resp):
|
|
|
|
results = []
|
|
|
|
|
|
|
|
search_results = etree.XML(resp.content)
|
|
|
|
|
|
|
|
# handle errors
|
|
|
|
# https://newznab.readthedocs.io/en/latest/misc/api/#newznab-error-codes
|
|
|
|
if search_results.tag == "error":
|
|
|
|
raise SearxEngineAPIException(search_results.get("description"))
|
|
|
|
|
|
|
|
for item in search_results[0].iterfind('item'):
|
|
|
|
result = {'template': 'torrent.html'}
|
|
|
|
|
|
|
|
enclosure = item.find('enclosure')
|
|
|
|
|
|
|
|
result["filesize"] = int(enclosure.get('length'))
|
|
|
|
|
|
|
|
link = get_property(item, 'link')
|
|
|
|
guid = get_property(item, 'guid')
|
|
|
|
comments = get_property(item, 'comments')
|
|
|
|
|
|
|
|
# define url
|
|
|
|
result["url"] = enclosure.get('url')
|
|
|
|
if comments is not None and comments.startswith('http'):
|
|
|
|
result["url"] = comments
|
|
|
|
elif guid is not None and guid.startswith('http'):
|
|
|
|
result["url"] = guid
|
|
|
|
|
|
|
|
# define torrent file url
|
|
|
|
result["torrentfile"] = None
|
|
|
|
if enclosure.get('url').startswith("http"):
|
|
|
|
result["torrentfile"] = enclosure.get('url')
|
|
|
|
elif link is not None and link.startswith('http'):
|
|
|
|
result["torrentfile"] = link
|
|
|
|
|
|
|
|
# define magnet link
|
|
|
|
result["magnetlink"] = get_torznab_attr(item, 'magneturl')
|
|
|
|
if result["magnetlink"] is None:
|
|
|
|
if enclosure.get('url').startswith("magnet"):
|
|
|
|
result["magnetlink"] = enclosure.get('url')
|
|
|
|
elif link is not None and link.startswith('magnet'):
|
|
|
|
result["magnetlink"] = link
|
|
|
|
|
|
|
|
result["title"] = get_property(item, 'title')
|
|
|
|
result["files"] = get_property(item, 'files')
|
|
|
|
|
|
|
|
result["publishedDate"] = None
|
|
|
|
try:
|
|
|
|
result["publishedDate"] = datetime.strptime(
|
|
|
|
get_property(item, 'pubDate'), '%a, %d %b %Y %H:%M:%S %z')
|
|
|
|
except (ValueError, TypeError) as e:
|
2021-10-07 07:35:50 +00:00
|
|
|
logger.debug("ignore exception (publishedDate): %s", e)
|
2021-10-07 00:34:42 +00:00
|
|
|
|
|
|
|
result["seed"] = get_torznab_attr(item, 'seeders')
|
|
|
|
|
|
|
|
# define leech
|
|
|
|
result["leech"] = get_torznab_attr(item, 'leechers')
|
|
|
|
if result["leech"] is None and result["seed"] is not None:
|
|
|
|
peers = get_torznab_attr(item, 'peers')
|
|
|
|
if peers is not None:
|
|
|
|
result["leech"] = int(peers) - int(result["seed"])
|
|
|
|
|
|
|
|
results.append(result)
|
|
|
|
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
|
def get_property(item, property_name):
|
|
|
|
property_element = item.find(property_name)
|
|
|
|
|
|
|
|
if property_element is not None:
|
|
|
|
return property_element.text
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def get_torznab_attr(item, attr_name):
|
|
|
|
element = item.find(
|
|
|
|
'.//torznab:attr[@name="{attr_name}"]'.format(attr_name=attr_name),
|
|
|
|
{
|
|
|
|
'torznab': 'http://torznab.com/schemas/2015/feed'
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
if element is not None:
|
|
|
|
return element.get("value")
|
|
|
|
|
|
|
|
return None
|