From d803df8d89c728af5e1fa826301060b221d623e2 Mon Sep 17 00:00:00 2001 From: Paolo Basso Date: Thu, 7 Oct 2021 02:34:42 +0200 Subject: [PATCH] [mod] engines - add torznab WebAPI --- searx/engines/torznab.py | 144 +++++++++++++++++++++++++++++++++++++++ searx/settings.yml | 13 ++++ 2 files changed, 157 insertions(+) create mode 100644 searx/engines/torznab.py diff --git a/searx/engines/torznab.py b/searx/engines/torznab.py new file mode 100644 index 000000000..aa9919c34 --- /dev/null +++ b/searx/engines/torznab.py @@ -0,0 +1,144 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""Torznab WebAPI + +A engine that implements the `torznab WebAPI`_. + +.. _torznab WebAPI: https://torznab.github.io/spec-1.3-draft/torznab + +""" + +from urllib.parse import quote +from lxml import etree +from datetime import datetime + +from searx.exceptions import SearxEngineAPIException + +# about +about = { + "website": None, + "wikidata_id": None, + "official_api_documentation": "https://torznab.github.io/spec-1.3-draft/torznab/Specification-v1.3.html#torznab-api-specification", + "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: + pass + + 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 diff --git a/searx/settings.yml b/searx/settings.yml index a46a4e913..0cdde3de9 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -1654,6 +1654,19 @@ engines: require_api_key: false results: HTML +# torznab engine lets you query any torznab compatible indexer. +# Using this engine in combination with Jackett (https://github.com/Jackett/Jackett) +# opens the possibility to query a lot of public and private indexers directly from searXNG. +# - name: torznab +# engine: torznab +# shortcut: trz +# base_url: http://localhost:9117/api/v2.0/indexers/all/results/torznab +# enable_http: true # if using localhost +# api_key: xxxxxxxxxxxxxxx +# torznab_categories: # optional +# - 2000 +# - 5000 + # Doku engine lets you access to any Doku wiki instance: # A public one or a privete/corporate one. # - name: ubuntuwiki