| 
							
							# SPDX-License-Identifier: AGPL-3.0-or-later
 | 
						
						
						
						
							 | 
							
							# lint: pylint
 | 
						
						
						
						
							 | 
							
							"""Torznab WebAPI
 | 
						
						
						
						
							 | 
							
							
 | 
						
						
						
						
							 | 
							
							A engine that implements the `torznab WebAPI`_.
 | 
						
						
						
						
							 | 
							
							
 | 
						
						
						
						
							 | 
							
							.. _torznab WebAPI: https://torznab.github.io/spec-1.3-draft/torznab
 | 
						
						
						
						
							 | 
							
							
 | 
						
						
						
						
							 | 
							
							"""
 | 
						
						
						
						
							 | 
							
							
 | 
						
						
						
						
							 | 
							
							from datetime import datetime
 | 
						
						
						
						
							 | 
							
							from urllib.parse import quote
 | 
						
						
						
						
							 | 
							
							from lxml import etree
 | 
						
						
						
						
							 | 
							
							
 | 
						
						
						
						
							 | 
							
							from searx.exceptions import SearxEngineAPIException
 | 
						
						
						
						
							 | 
							
							
 | 
						
						
						
						
							 | 
							
							# about
 | 
						
						
						
						
							 | 
							
							about = {
 | 
						
						
						
						
							 | 
							
							    "website": None,
 | 
						
						
						
						
							 | 
							
							    "wikidata_id": None,
 | 
						
						
						
						
							 | 
							
							    "official_api_documentation": "https://torznab.github.io/spec-1.3-draft",
 | 
						
						
						
						
							 | 
							
							    "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 init(engine_settings=None):  # pylint: disable=unused-argument
 | 
						
						
						
						
							 | 
							
							    if len(base_url) < 1:
 | 
						
						
						
						
							 | 
							
							        raise ValueError('missing torznab base_url')
 | 
						
						
						
						
							 | 
							
							
 | 
						
						
						
						
							 | 
							
							
 | 
						
						
						
						
							 | 
							
							def request(query, params):
 | 
						
						
						
						
							 | 
							
							
 | 
						
						
						
						
							 | 
							
							    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([str(x) for x in 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:
 | 
						
						
						
						
							 | 
							
							            logger.debug("ignore exception (publishedDate): %s", e)
 | 
						
						
						
						
							 | 
							
							
 | 
						
						
						
						
							 | 
							
							        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
 |