From 58b5db05321f409079d3cd872f67514a8b127764 Mon Sep 17 00:00:00 2001 From: dnzbk Date: Mon, 29 Jul 2024 15:57:56 +0300 Subject: [PATCH 1/4] init --- .github/workflows/release.yml | 14 ++++ .github/workflows/tests.yml | 19 ++++++ README.md | 9 +++ main.py | 118 ++++++++++++++++++++++++++++++++++ manifest.json | 70 ++++++++++++++++++++ 5 files changed, 230 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/tests.yml create mode 100644 README.md create mode 100644 main.py create mode 100644 manifest.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d0a5695 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,14 @@ +name: release + +on: + push: + tags: + - "v*" + +jobs: + release: + uses: nzbgetcom/nzbget-extensions/.github/workflows/extension-release.yml@main + with: + release-file-list: main.py manifest.json + release-file-name: notifyembyjellyfin + release-dir: NotifyEmbyJellyfin diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..0edbfdd --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,19 @@ +name: tests + +on: + push: + branches: + - feature/* + - main + pull_request: + branches: + - main + +jobs: + tests: + uses: nzbgetcom/nzbget-extensions/.github/workflows/python-tests.yml@main + with: + python-versions: "3.3 3.4 3.5 3.6 3.7 3.8 3.9 3.10 3.11 3.12" + supported-python-versions: "3.8 3.9 3.10 3.11 3.12" + test-script: tests.py + debug: true diff --git a/README.md b/README.md new file mode 100644 index 0000000..baf3232 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +>This extension is compatible to Python 3.8 and above. + +## NZBGet Versions + +- stable v23 [v1.0](https://github.com/nzbgetcom/Extension-NotifyEmbyJellyfin/releases/tag/v1.0) + +# Notify Emby or Jellyfin + +NZBGet [Post-Processing](https://github.com/nzbgetcom/nzbget/blob/develop/docs/extensions/POST-PROCESSING.md) extension to trigger Emby or Jellyfin after downloads. diff --git a/main.py b/main.py new file mode 100644 index 0000000..454253d --- /dev/null +++ b/main.py @@ -0,0 +1,118 @@ +# +# This file is part of nzbget. See . +# +# Copyright (C) 2024 Denis +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + + +import os +import sys +import urllib.parse +import urllib.request + +SUCCESS = 93 +ERROR = 94 +NONE = 95 + + +REQUIRED_OPTIONS = [ + "NZBPO_APIKEY", + "NZBPO_HOST", + "NZBPO_PORT", +] + + +def validate_options(options: list) -> None: + for optname in options: + if optname not in os.environ: + print( + f"[ERROR] Option {optname[6:]} is missing in configuration file. Please check extension settings." + ) + sys.exit(ERROR) + + +validate_options(REQUIRED_OPTIONS) + + +API_KEY = os.environ["NZBPO_APIKEY"] +HOST = os.environ["NZBPO_HOST"] +PORT = os.environ["NZBPO_PORT"] + + +VERBOSE = os.environ["NZBPO_VERBOSE"] == "yes" +COMMAND = os.environ.get("NZBCP_COMMAND") == "ping" + +URL = f"http://{HOST}:{PORT}" + + +if VERBOSE: + print("[INFO] URL:", URL) + + +def ping_server(url: str) -> int: + req_url = f"{url}/System/Ping" + + if VERBOSE: + print(f"[INFO] REQUEST URL: {req_url}") + + try: + req = urllib.request.Request(req_url) + with urllib.request.urlopen(req) as response: + data = response.read().decode("utf-8") + + print( + f"[INFO] Server pinged successfully: {data}", + ) + return SUCCESS + + except Exception as ex: + print(f"[ERROR] Unexpected exception: {ex}. Wrong API key?") + return ERROR + + +def refresh_library(url) -> int: + req_url = f"{url}/Library/Refresh" + headers = {"Authorization": f'MediaBrowser Client="NZBGet", Token="{API_KEY}"'} + + if VERBOSE: + print(f"[INFO] REQUEST URL: {req_url}") + print(f"[INFO] HEADERS: {headers}") + + try: + req = urllib.request.Request(req_url, headers=headers, method="POST") + with urllib.request.urlopen(req) as response: + data = response.read().decode("utf-8") + + print(f"[INFO] The library refreshed successfully: {data}") + return SUCCESS + + except Exception as ex: + print(f"[ERROR] Unexpected exception: {ex}") + return ERROR + + +if COMMAND: + sys.exit(ping_server(URL)) + + +PATH = os.environ.get("NZBPP_FINALDIR") or os.environ["NZBPP_DIRECTORY"] + + +if VERBOSE: + print(f"[INFO] PATH: {PATH}") + + +sys.exit(refresh_library(URL)) diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..a407ebd --- /dev/null +++ b/manifest.json @@ -0,0 +1,70 @@ +{ + "main": "main.py", + "name": "NotifyEmbyJellyfin", + "homepage": "https://github.com/nzbgetcom/Extension-NotifyEmbyJellyfin/", + "kind": "POST-PROCESSING", + "displayName": "Notify Emby/Jellyfin", + "version": "1.0", + "nzbgetMinVersion": "23.0", + "author": "Denis", + "license": "GNU", + "about": "Post-Processing extension to trigger Emby/Jellyfin after downloads.", + "queueEvents": "", + "description": [], + "requirements": [ + "This extension is compatible to Python 3.8 and above." + ], + "options": [ + { + "name": "host", + "displayName": "Host", + "value": "127.0.0.1", + "description": [ + "Emby/Jellyfin server host." + ], + "select": [] + }, + { + "name": "port", + "displayName": "Port", + "value": 8096, + "description": [ + "Default port is: 8096" + ], + "select": [1, 65535] + }, + { + "name": "apiKey", + "displayName": "API Key", + "value": "", + "description": [ + "Used to authenticate and authorize access to an Emby/Jellyfin API.", + "", + "Can be created in the settings of the web interface of Emby/Jellyfin." + ], + "select": [] + }, + { + "name": "Verbose", + "displayName": "Verbose", + "value": "no", + "description": [ + "Print more logging messages.", + "", + "For debugging." + ], + "select": ["no", "yes"] + } + ], + "commands": [ + { + "name": "ping", + "displayName": "Ping", + "action": "Ping Emby/Jellyfin", + "description": [ + "Ping Emby/Jellyfin to check if it is running." + ] + } + ], + "taskTime": "" +} \ No newline at end of file From 833751317d914d950b0e11bf874f250656da5fa4 Mon Sep 17 00:00:00 2001 From: dnzbk Date: Mon, 29 Jul 2024 17:01:31 +0300 Subject: [PATCH 2/4] Add: tests --- tests.py | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 tests.py diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..4d1b18d --- /dev/null +++ b/tests.py @@ -0,0 +1,101 @@ +# +# This file is part of nzbget. See . +# +# Copyright (C) 2024 Denis +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + + +import sys +from os.path import dirname +import os +import subprocess +import http.server +import threading +import unittest +import json +from urllib.parse import urlparse, parse_qs + +SUCCESS = 93 +NONE = 95 +ERROR = 94 + +ROOT_DIR = dirname(__file__) +HOST = "127.0.0.1" +PORT = "8096" + + +class HttpServerPingMock(http.server.BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header("Content-type", "application/json") + self.end_headers() + + data = {"data": {"pid": 5124}, "message": "Pong", "result": "success"} + response = json.dumps(data) + self.wfile.write(response.encode("utf-8")) + + +def get_python(): + if os.name == "nt": + return "python" + return "python3" + + +def run_script(): + sys.stdout.flush() + proc = subprocess.Popen( + [get_python(), ROOT_DIR + "/main.py"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=os.environ.copy(), + ) + out, err = proc.communicate() + ret_code = proc.returncode + return (out.decode(), int(ret_code), err.decode()) + + +def set_default_env(): + os.environ["NZBPO_APIKEY"] = "API_KEY" + os.environ["NZBPP_DIRECTORY"] = ROOT_DIR + os.environ["NZBPO_HOST"] = HOST + os.environ["NZBPO_PORT"] = PORT + os.environ["NZBPO_VERBOSE"] = "yes" + + +class Tests(unittest.TestCase): + def test_command(self): + set_default_env() + os.environ["NZBCP_COMMAND"] = "ping" + server = http.server.HTTPServer((HOST, int(PORT)), HttpServerPingMock) + thread = threading.Thread(target=server.serve_forever) + thread.start() + [_, code, _] = run_script() + server.shutdown() + server.server_close() + thread.join() + self.assertEqual(code, SUCCESS) + + + def test_manifest(self): + with open(ROOT_DIR + "/manifest.json", encoding="utf-8") as file: + try: + json.loads(file.read()) + except ValueError as e: + self.fail("manifest.json is not valid.") + + +if __name__ == "__main__": + unittest.main() From 4000b79a61ff1b5d30317ec3a0b4c577e6ebc801 Mon Sep 17 00:00:00 2001 From: dnzbk Date: Tue, 30 Jul 2024 08:51:33 +0300 Subject: [PATCH 3/4] Add: API tests --- tests.py | 53 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/tests.py b/tests.py index 4d1b18d..cdfc779 100644 --- a/tests.py +++ b/tests.py @@ -26,7 +26,7 @@ import threading import unittest import json -from urllib.parse import urlparse, parse_qs +from urllib.parse import urlparse SUCCESS = 93 NONE = 95 @@ -37,15 +37,41 @@ PORT = "8096" -class HttpServerPingMock(http.server.BaseHTTPRequestHandler): +class HttpServerGetMock(http.server.BaseHTTPRequestHandler): def do_GET(self): + parsed_url = urlparse(self.path) + + data = "" + if parsed_url.path == "/System/Ping": + data = "success" + self.send_response(200) + else: + data = "failure" + self.send_response(400) + self.send_response(200) - self.send_header("Content-type", "application/json") + self.send_header("Content-type", "text/plain") self.end_headers() - data = {"data": {"pid": 5124}, "message": "Pong", "result": "success"} - response = json.dumps(data) - self.wfile.write(response.encode("utf-8")) + data = "success" + self.wfile.write(data.encode("utf-8")) + + +class HttpPostMock(http.server.BaseHTTPRequestHandler): + def do_POST(self): + parsed_url = urlparse(self.path) + + data = "" + if parsed_url.path == "/Library/Refresh": + data = "success" + self.send_response(200) + else: + data = "failure" + self.send_response(400) + + self.send_header("Content-type", "text/plain") + self.end_headers() + self.wfile.write(data.encode("utf-8")) def get_python(): @@ -79,7 +105,7 @@ class Tests(unittest.TestCase): def test_command(self): set_default_env() os.environ["NZBCP_COMMAND"] = "ping" - server = http.server.HTTPServer((HOST, int(PORT)), HttpServerPingMock) + server = http.server.HTTPServer((HOST, int(PORT)), HttpServerGetMock) thread = threading.Thread(target=server.serve_forever) thread.start() [_, code, _] = run_script() @@ -88,13 +114,24 @@ def test_command(self): thread.join() self.assertEqual(code, SUCCESS) + def test_refresh_lib(self): + set_default_env() + os.environ.pop("NZBCP_COMMAND", None) + server = http.server.HTTPServer((HOST, int(PORT)), HttpPostMock) + thread = threading.Thread(target=server.serve_forever) + thread.start() + [_, code, _] = run_script() + server.shutdown() + server.server_close() + thread.join() + self.assertEqual(code, SUCCESS) def test_manifest(self): with open(ROOT_DIR + "/manifest.json", encoding="utf-8") as file: try: json.loads(file.read()) except ValueError as e: - self.fail("manifest.json is not valid.") + self.fail(f"manifest.json is not valid: {e}") if __name__ == "__main__": From 9d09c43e113d068820bc6040f372425437007454 Mon Sep 17 00:00:00 2001 From: dnzbk Date: Tue, 30 Jul 2024 09:19:52 +0300 Subject: [PATCH 4/4] Fix: typo in requirements --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index a407ebd..f33d1d5 100644 --- a/manifest.json +++ b/manifest.json @@ -12,7 +12,7 @@ "queueEvents": "", "description": [], "requirements": [ - "This extension is compatible to Python 3.8 and above." + "This extension supports Python 3.8 and above." ], "options": [ {