Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions nova3/engines/jackett.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# VERSION: 4.13
# VERSION: 4.14
# AUTHORS: Diego de las Heras (ngosang@hotmail.es)
# CONTRIBUTORS: ukharley
# hannsen (github.com/hannsen)
Expand All @@ -7,6 +7,7 @@
import datetime
import json
import os
import sys
import urllib.request
import xml.etree.ElementTree
from http.cookiejar import CookieJar
Expand Down Expand Up @@ -146,15 +147,15 @@ def search(self, what: str, cat: str = 'all') -> None:

# check api_key
if self.api_key == "YOUR_API_KEY_HERE":
self.handle_error("api key error", what)
self.handle_error("API key not configured in jackett.json", what)
return

# search in Jackett API
if self.thread_count > 1:
indexers = self.get_jackett_indexers(what)
if not indexers:
return
indexers = self.get_jackett_indexers(what)
if not indexers:
return

if self.thread_count > 1:
args = ((what, category, indexer) for indexer in indexers)
with Pool(min(len(indexers), self.thread_count)) as pool:
pool.starmap(self.search_jackett_indexer, args)
Expand All @@ -170,13 +171,21 @@ def get_jackett_indexers(self, what: str) -> List[str]:
jacket_url = f"{self.url}/api/v2.0/indexers/all/results/torznab/api?{params}"
response = self.get_response(jacket_url)
if response is None:
self.handle_error("connection error getting indexer list", what)
self.handle_error("cannot contact Jackett; check that it is running and the API key is correct", what)
return []
# process results
response_xml = xml.etree.ElementTree.fromstring(response)
try:
response_xml = xml.etree.ElementTree.fromstring(response)
except xml.etree.ElementTree.ParseError as error:
raise RuntimeError(f"Invalid response from Jackett. {what}") from error
if response_xml.tag == 'error':
description = response_xml.attrib.get('description', 'unknown error')
raise RuntimeError(f"Jackett API error: {description}. {what}")
indexers: List[str] = []
for indexer in response_xml.findall('indexer'):
indexers.append(indexer.attrib['id'])
if not indexers:
self.handle_error("no indexers configured in Jackett; add one in the Jackett UI", what)
return indexers

def search_jackett_indexer(self, what: str, category: Union[List[str], None], indexer_id: str) -> None:
Expand Down Expand Up @@ -278,6 +287,7 @@ def get_response(self, query: str) -> Union[str, None]:
return response

def handle_error(self, error_msg: str, what: str) -> None:
print(f"Jackett: {error_msg}. Search: {what}", file=sys.stderr)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are already passing the error to stdout so don't do this.

@q5x-v6e-n7z-6x3
Clearly you are using AI. Please instruct your AI to follow/address the review comments closely and don't invent new ideas.

# we need to print the search text to be displayed in qBittorrent when
# 'Torrent names only' is enabled
self.pretty_printer_thread_safe({
Expand Down
2 changes: 1 addition & 1 deletion nova3/engines/versions.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
eztv: 1.24
jackett: 4.13
jackett: 4.14
limetorrents: 4.15
piratebay: 3.10
solidtorrents: 2.9
Expand Down
59 changes: 57 additions & 2 deletions nova3/tests/test_jackett.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from __future__ import annotations

from unittest.mock import Mock

import pytest

from ..engines import jackett
Comment on lines +1 to 7

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, follow the coding style from #464 (comment)

Expand All @@ -8,5 +12,56 @@ def test_jackett(capfd: pytest.CaptureFixture[str]) -> None:
engine.search('linux', 'all')

capturedOutput = capfd.readouterr()
assert capturedOutput.err == ""
assert len(capturedOutput.out) >= 0
Comment on lines -11 to -12

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert this.
Don't touch existing code unless it is broken.

assert capturedOutput.err or capturedOutput.out


def test_jackett_reports_missing_api_key(capfd: pytest.CaptureFixture[str]) -> None:
engine = jackett.jackett()
engine.api_key = 'YOUR_API_KEY_HERE'
engine.search('linux', 'all')

assert capfd.readouterr().err


@pytest.mark.parametrize(
'response',
[
None,
'<indexers />',
],
)
def test_jackett_reports_setup_errors(monkeypatch: pytest.MonkeyPatch, capfd: pytest.CaptureFixture[str], response: str | None) -> None:
engine = jackett.jackett()
engine.api_key = 'test-key'
engine.thread_count = 1

monkeypatch.setattr(engine, 'get_response', Mock(return_value=response))
engine.search('linux', 'all')

assert capfd.readouterr().err


@pytest.mark.parametrize('response', [
'<indexers',
'<error code="100" description="Invalid API Key" />',
])
def test_jackett_raises_for_bad_response(monkeypatch: pytest.MonkeyPatch, response: str) -> None:
engine = jackett.jackett()
engine.api_key = 'test-key'

monkeypatch.setattr(engine, 'get_response', Mock(return_value=response))

with pytest.raises(RuntimeError):
engine.search('linux', 'all')


def test_jackett_checks_indexers_without_concurrency(monkeypatch: pytest.MonkeyPatch) -> None:
engine = jackett.jackett()
engine.api_key = 'test-key'
engine.thread_count = 1
search_mock = Mock()
monkeypatch.setattr(engine, 'get_response', Mock(return_value='<indexers><indexer id="thepiratebay" /></indexers>'))
Comment on lines +62 to +63

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
search_mock = Mock()
monkeypatch.setattr(engine, 'get_response', Mock(return_value='<indexers><indexer id="thepiratebay" /></indexers>'))
monkeypatch.setattr(engine, 'get_response', Mock(return_value='<indexers><indexer id="thepiratebay" /></indexers>'))
search_mock = Mock()

monkeypatch.setattr(engine, 'search_jackett_indexer', search_mock)
engine.search('linux', 'all')

search_mock.assert_called_once_with('linux', None, 'all')