From 0827c5bafe6d9376062b3c47da4f1a0d9dc1d8a6 Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Mon, 13 Jan 2020 21:17:33 +1300 Subject: [PATCH 01/14] add SABnzbd environment variable handling. #1689 (#1701) --- nzbToMedia.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nzbToMedia.py b/nzbToMedia.py index 073cb6f14..89ed8a8e0 100755 --- a/nzbToMedia.py +++ b/nzbToMedia.py @@ -959,6 +959,13 @@ def main(args, section=None): result = process(os.environ['NZBPP_DIRECTORY'], input_name=os.environ['NZBPP_NZBNAME'], status=status, client_agent=client_agent, download_id=download_id, input_category=os.environ['NZBPP_CATEGORY'], failure_link=failure_link) + # SABnzbd + elif 'SAB_SCRIPT' in os.environ: + client_agent = 'sabnzbd' + logger.info('Script triggered from SABnzbd Version {0}.'.format(os.environ['SAB_VERSION'])) + result = process(os.environ['SAB_COMPLETE_DIR'], input_name=os.environ['SAB_FINAL_NAME'], status=int(os.environ['SAB_PP_STATUS']), + client_agent=client_agent, download_id=os.environ['SAB_NZO_ID'], input_category=os.environ['SAB_CAT'], + failure_link=os.environ['SAB_FAILURE_URL']) # SABnzbd Pre 0.7.17 elif len(args) == core.SABNZB_NO_OF_ARGUMENTS: # SABnzbd argv: From b793ce7933de9f261cb0a1c37d481d1a5877d990 Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Mon, 13 Jan 2020 21:26:21 +1300 Subject: [PATCH 02/14] Syno ds patch 1 (#1702) * Add Syno DS parsing #1671 as per https://forum.synology.com/enu/viewtopic.php?f=38&t=92856 * add config guidance * add syno client --- autoProcessMedia.cfg.spec | 7 +- core/__init__.py | 5 + .../downloaders/torrent/configuration.py | 10 +- core/plugins/downloaders/torrent/synology.py | 27 ++ core/plugins/downloaders/torrent/utils.py | 8 + core/utils/parsers.py | 32 ++ libs/custom/syno/__init__.py | 0 libs/custom/syno/auth.py | 124 ++++++++ libs/custom/syno/downloadstation.py | 276 ++++++++++++++++++ 9 files changed, 487 insertions(+), 2 deletions(-) create mode 100644 core/plugins/downloaders/torrent/synology.py create mode 100644 libs/custom/syno/__init__.py create mode 100644 libs/custom/syno/auth.py create mode 100644 libs/custom/syno/downloadstation.py diff --git a/autoProcessMedia.cfg.spec b/autoProcessMedia.cfg.spec index 7af48e011..bcd6b528c 100644 --- a/autoProcessMedia.cfg.spec +++ b/autoProcessMedia.cfg.spec @@ -355,7 +355,7 @@ default_downloadDirectory = [Torrent] - ###### clientAgent - Supported clients: utorrent, transmission, deluge, rtorrent, vuze, qbittorrent, other + ###### clientAgent - Supported clients: utorrent, transmission, deluge, rtorrent, vuze, qbittorrent, synods, other clientAgent = other ###### useLink - Set to hard for physical links, sym for symbolic links, move to move, move-sym to move and link back, and no to not use links (copy) useLink = hard @@ -386,6 +386,11 @@ qBittorrentPort = 8080 qBittorrentUSR = your username qBittorrentPWD = your password + ###### Synology Download Station (You must edit this if you're using TorrentToMedia.py with Synology DS) + synoHost = localhost + synoPort = 9091 + synoUSR = your username + synoPWD = your password ###### ADVANCED USE - ONLY EDIT IF YOU KNOW WHAT YOU'RE DOING ###### deleteOriginal = 0 chmodDirectory = 0 diff --git a/core/__init__.py b/core/__init__.py index 8cdcfeee9..7198ab94a 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -178,6 +178,11 @@ TRANSMISSION_USER = None TRANSMISSION_PASSWORD = None +SYNO_HOST = None +SYNO_PORT = None +SYNO_USER = None +SYNO_PASSWORD = None + DELUGE_HOST = None DELUGE_PORT = None DELUGE_USER = None diff --git a/core/plugins/downloaders/torrent/configuration.py b/core/plugins/downloaders/torrent/configuration.py index dd8e8ffa7..e89c883f5 100644 --- a/core/plugins/downloaders/torrent/configuration.py +++ b/core/plugins/downloaders/torrent/configuration.py @@ -11,7 +11,7 @@ def configure_torrents(config): torrent_config = config['Torrent'] - core.TORRENT_CLIENT_AGENT = torrent_config['clientAgent'] # utorrent | deluge | transmission | rtorrent | vuze | qbittorrent |other + core.TORRENT_CLIENT_AGENT = torrent_config['clientAgent'] # utorrent | deluge | transmission | rtorrent | vuze | qbittorrent | synods | other core.OUTPUT_DIRECTORY = torrent_config['outputDirectory'] # /abs/path/to/complete/ core.TORRENT_DEFAULT_DIRECTORY = torrent_config['default_downloadDirectory'] @@ -25,6 +25,7 @@ def configure_torrents(config): configure_transmission(torrent_config) configure_deluge(torrent_config) configure_qbittorrent(torrent_config) + configure_syno(torrent_config) def configure_torrent_linking(config): @@ -69,6 +70,13 @@ def configure_transmission(config): core.TRANSMISSION_PASSWORD = config['TransmissionPWD'] # mysecretpwr +def configure_syno(config): + core.SYNO_HOST = config['synoHost'] # localhost + core.SYNO_PORT = int(config['synoPort']) + core.SYNO_USER = config['synoUSR'] # mysecretusr + core.SYNO_PASSWORD = config['synoPWD'] # mysecretpwr + + def configure_deluge(config): core.DELUGE_HOST = config['DelugeHost'] # localhost core.DELUGE_PORT = int(config['DelugePort']) # 8084 diff --git a/core/plugins/downloaders/torrent/synology.py b/core/plugins/downloaders/torrent/synology.py new file mode 100644 index 000000000..68860629b --- /dev/null +++ b/core/plugins/downloaders/torrent/synology.py @@ -0,0 +1,27 @@ +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) + +from syno.downloadstation import DownloadStation + +import core +from core import logger + + +def configure_client(): + agent = 'synology' + host = core.SYNO_HOST + port = core.SYNO_PORT + user = core.SYNO_USER + password = core.SYNO_PASSWORD + + logger.debug('Connecting to {0}: http://{1}:{2}'.format(agent, host, port)) + try: + client = DownloadStation(host, port, user, password) + except Exception: + logger.error('Failed to connect to synology') + else: + return client diff --git a/core/plugins/downloaders/torrent/utils.py b/core/plugins/downloaders/torrent/utils.py index 0d2896d7e..f1a3cba1c 100644 --- a/core/plugins/downloaders/torrent/utils.py +++ b/core/plugins/downloaders/torrent/utils.py @@ -14,12 +14,14 @@ from .qbittorrent import configure_client as qbittorrent_client from .transmission import configure_client as transmission_client from .utorrent import configure_client as utorrent_client +from .synology import configure_client as synology_client torrent_clients = { 'deluge': deluge_client, 'qbittorrent': qbittorrent_client, 'transmission': transmission_client, 'utorrent': utorrent_client, + 'synods': synology_client, } @@ -39,6 +41,8 @@ def pause_torrent(client_agent, input_hash, input_id, input_name): core.TORRENT_CLASS.stop(input_hash) if client_agent == 'transmission' and core.TORRENT_CLASS != '': core.TORRENT_CLASS.stop_torrent(input_id) + if client_agent == 'synods' and core.TORRENT_CLASS != '': + core.TORRENT_CLASS.pause_task(input_id) if client_agent == 'deluge' and core.TORRENT_CLASS != '': core.TORRENT_CLASS.core.pause_torrent([input_id]) if client_agent == 'qbittorrent' and core.TORRENT_CLASS != '': @@ -57,6 +61,8 @@ def resume_torrent(client_agent, input_hash, input_id, input_name): core.TORRENT_CLASS.start(input_hash) if client_agent == 'transmission' and core.TORRENT_CLASS != '': core.TORRENT_CLASS.start_torrent(input_id) + if client_agent == 'synods' and core.TORRENT_CLASS != '': + core.TORRENT_CLASS.resume_task(input_id) if client_agent == 'deluge' and core.TORRENT_CLASS != '': core.TORRENT_CLASS.core.resume_torrent([input_id]) if client_agent == 'qbittorrent' and core.TORRENT_CLASS != '': @@ -75,6 +81,8 @@ def remove_torrent(client_agent, input_hash, input_id, input_name): core.TORRENT_CLASS.remove(input_hash) if client_agent == 'transmission' and core.TORRENT_CLASS != '': core.TORRENT_CLASS.remove_torrent(input_id, True) + if client_agent == 'synods' and core.TORRENT_CLASS != '': + core.TORRENT_CLASS.delete_task(input_id) if client_agent == 'deluge' and core.TORRENT_CLASS != '': core.TORRENT_CLASS.core.remove_torrent(input_id, True) if client_agent == 'qbittorrent' and core.TORRENT_CLASS != '': diff --git a/core/utils/parsers.py b/core/utils/parsers.py index eff4b3e95..2fc5f0705 100644 --- a/core/utils/parsers.py +++ b/core/utils/parsers.py @@ -8,6 +8,7 @@ import os import core +from core import logger def parse_other(args): @@ -81,6 +82,36 @@ def parse_transmission(args): return input_directory, input_name, input_category, input_hash, input_id +def parse_synods(args): + # Synology/Transmission usage: call TorrenToMedia.py (%TR_TORRENT_DIR% %TR_TORRENT_NAME% is passed on as environmental variables) + input_directory = '' + input_id = '' + input_category = '' + input_name = os.getenv('TR_TORRENT_NAME') + input_hash = os.getenv('TR_TORRENT_HASH') + if not input_name: # No info passed. Assume manual download. + return input_directory, input_name, input_category, input_hash, input_id + input_id = 'dbid_'.format(os.getenv('TR_TORRENT_ID')) + #res = core.TORRENT_CLASS.tasks_list(additional_param='detail') + res = core.TORRENT_CLASS.tasks_info(input_id, additional_param='detail') + logger.debug('result from syno {0}'.format(res)) + if res['success']: + try: + tasks = res['data']['tasks'] + task = [ task for task in tasks if task['id'] == input_id ][0] + input_id = task['id'] + input_directory = task['additional']['detail']['destination'] + except: + logger.error('unable to find download details in Synology DS') + #Syno paths appear to be relative. Let's test to see if the returned path exists, and if not append to /volume1/ + if not os.path.isdir(input_directory): + for root in ['/volume1/', '/volume2/', '/volume3/', '/volume4/']: + if os.path.isdir(os.path.join(root, input_directory)): + input_directory = os.path.join(root, input_directory) + break + return input_directory, input_name, input_category, input_hash, input_id + + def parse_vuze(args): # vuze usage: C:\full\path\to\nzbToMedia\TorrentToMedia.py '%D%N%L%I%K%F' try: @@ -159,6 +190,7 @@ def parse_args(client_agent, args): 'transmission': parse_transmission, 'qbittorrent': parse_qbittorrent, 'vuze': parse_vuze, + 'synods': parse_synods, } try: diff --git a/libs/custom/syno/__init__.py b/libs/custom/syno/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/libs/custom/syno/auth.py b/libs/custom/syno/auth.py new file mode 100644 index 000000000..ef8d2f8ea --- /dev/null +++ b/libs/custom/syno/auth.py @@ -0,0 +1,124 @@ +import requests + + +class Authentication: + def __init__(self, ip_address, port, username, password): + self._ip_address = ip_address + self._port = port + self._username = username + self._password = password + self._sid = None + self._session_expire = True + self._base_url = 'http://%s:%s/webapi/' % (self._ip_address, self._port) + + self.full_api_list = {} + self.app_api_list = {} + + def login(self, application): + login_api = 'auth.cgi?api=SYNO.API.Auth' + param = {'version': '2', 'method': 'login', 'account': self._username, + 'passwd': self._password, 'session': application, 'format': 'cookie'} + + if not self._session_expire: + if self._sid is not None: + self._session_expire = False + return 'User already logged' + else: + session_request = requests.get(self._base_url + login_api, param) + self._sid = session_request.json()['data']['sid'] + self._session_expire = False + return 'User logging... New session started!' + + def logout(self, application): + logout_api = 'auth.cgi?api=SYNO.API.Auth' + param = {'version': '2', 'method': 'logout', 'session': application} + + response = requests.get(self._base_url + logout_api, param) + if response.json()['success'] is True: + self._session_expire = True + self._sid = None + return 'Logged out' + else: + self._session_expire = True + self._sid = None + return 'No valid session is open' + + def get_api_list(self, app=None): + query_path = 'query.cgi?api=SYNO.API.Info' + list_query = {'version': '1', 'method': 'query', 'query': 'all'} + + response = requests.get(self._base_url + query_path, list_query).json() + + if app is not None: + for key in response['data']: + if app.lower() in key.lower(): + self.app_api_list[key] = response['data'][key] + else: + self.full_api_list = response['data'] + + return + + def show_api_name_list(self): + prev_key = '' + for key in self.full_api_list: + if key != prev_key: + print(key) + prev_key = key + return + + def show_json_response_type(self): + for key in self.full_api_list: + for sub_key in self.full_api_list[key]: + if sub_key == 'requestFormat': + if self.full_api_list[key]['requestFormat'] == 'JSON': + print(key + ' Returns JSON data') + return + + def search_by_app(self, app): + print_check = 0 + for key in self.full_api_list: + if app.lower() in key.lower(): + print(key) + print_check += 1 + continue + if print_check == 0: + print('Not Found') + return + + def request_data(self, api_name, api_path, req_param, method=None, response_json=True): # 'post' or 'get' + + # Convert all booleen in string in lowercase because Synology API is waiting for "true" or "false" + for k,v in req_param.items(): + if isinstance(v, bool): + req_param[k] = str(v).lower() + + if method is None: + method = 'get' + + req_param['_sid'] = self._sid + + if method is 'get': + url = ('%s%s' % (self._base_url, api_path)) + '?api=' + api_name + response = requests.get(url, req_param) + + if response_json is True: + return response.json() + else: + return response + + elif method is 'post': + url = ('%s%s' % (self._base_url, api_path)) + '?api=' + api_name + response = requests.post(url, req_param) + + if response_json is True: + return response.json() + else: + return response + + @property + def sid(self): + return self._sid + + @property + def base_url(self): + return self._base_url \ No newline at end of file diff --git a/libs/custom/syno/downloadstation.py b/libs/custom/syno/downloadstation.py new file mode 100644 index 000000000..b70213687 --- /dev/null +++ b/libs/custom/syno/downloadstation.py @@ -0,0 +1,276 @@ +from . import auth as syn + + +class DownloadStation: + + def __init__(self, ip_address, port, username, password): + + self.session = syn.Authentication(ip_address, port, username, password) + self._bt_search_id = '' + self._bt_search_id_list = [] + self.session.login('DownloadStation') + self.session.get_api_list('DownloadStation') + + self.request_data = self.session.request_data + self.download_list = self.session.app_api_list + self._sid = self.session.sid + self.base_url = self.session.base_url + + print('You are now logged in!') + + def logout(self): + self.session.logout('DownloadStation') + + def get_info(self): + api_name = 'SYNO.DownloadStation.Info' + info = self.download_list[api_name] + api_path = info['path'] + req_param = {'version': info['maxVersion'], 'method': 'getinfo'} + + return self.request_data(api_name, api_path, req_param) + + def get_config(self): + api_name = 'SYNO.DownloadStation.Info' + info = self.download_list[api_name] + api_path = info['path'] + req_param = {'version': info['maxVersion'], 'method': 'getconfig'} + + return self.request_data(api_name, api_path, req_param) + + def set_server_config(self, bt_max_download=None, bt_max_upload=None, emule_max_download=None, + emule_max_upload=None, nzb_max_download=None, http_max_download=None, ftp_max_download=None, + emule_enabled=None, unzip_service_enabled=None, default_destination=None, + emule_default_destination=None): + + api_name = 'SYNO.DownloadStation.Info' + info = self.download_list[api_name] + api_path = info['path'] + req_param = {'version': info['maxVersion'], 'method': 'setserverconfig'} + + for key, val in locals().items(): + if key not in ['self', 'api_name', 'info', 'api_path', 'req_param']: + if val is not None: + req_param[str(key)] = val + + return self.request_data(api_name, api_path, req_param) + + def schedule_info(self): + api_name = 'SYNO.DownloadStation.Schedule' + info = self.download_list[api_name] + api_path = info['path'] + req_param = {'version': info['maxVersion'], 'method': 'getconfig'} + + return self.request_data(api_name, api_path, req_param) + + def schedule_set_config(self, enabled=False, emule_enabled=False): + api_name = 'SYNO.DownloadStation.Schedule' + info = self.download_list[api_name] + api_path = info['path'] + req_param = {'version': info['maxVersion'], 'method': 'setconfig', 'enabled': str(enabled).lower(), + 'emule_enabled': str(emule_enabled).lower()} + + if type(enabled) is not bool or type(emule_enabled) is not bool: + return 'Please set enabled to True or False' + + return self.request_data(api_name, api_path, req_param) + + def tasks_list(self, additional_param=None): + api_name = 'SYNO.DownloadStation.Task' + info = self.download_list[api_name] + api_path = info['path'] + req_param = {'version': info['maxVersion'], 'method': 'list', 'additional': additional_param} + + if additional_param is None: + additional_param = ['detail', 'transfer', 'file', 'tracker', 'peer'] + + if type(additional_param) is list: + req_param['additional'] = ",".join(additional_param) + + return self.request_data(api_name, api_path, req_param) + + def tasks_info(self, task_id, additional_param=None): + api_name = 'SYNO.DownloadStation.Task' + info = self.download_list[api_name] + api_path = info['path'] + req_param = {'version': info['maxVersion'], 'method': 'getinfo', 'id': task_id, 'additional': additional_param} + + if additional_param is None: + additional_param = ['detail', 'transfer', 'file', 'tracker', 'peer'] + + if type(additional_param) is list: + req_param['additional'] = ",".join(additional_param) + + if type(task_id) is list: + req_param['id'] = ",".join(task_id) + + return self.request_data(api_name, api_path, req_param) + + def delete_task(self, task_id, force=False): + api_name = 'SYNO.DownloadStation.Task' + info = self.download_list[api_name] + api_path = info['path'] + param = {'version': info['maxVersion'], 'method': 'delete', 'id': task_id, + 'force_complete': str(force).lower()} + + if type(task_id) is list: + param['id'] = ",".join(task_id) + + return self.request_data(api_name, api_path, param) + + def pause_task(self, task_id): + api_name = 'SYNO.DownloadStation.Task' + info = self.download_list[api_name] + api_path = info['path'] + param = {'version': info['maxVersion'], 'method': 'pause', 'id': task_id} + + if type(task_id) is list: + param['id'] = ",".join(task_id) + + return self.request_data(api_name, api_path, param) + + def resume_task(self, task_id): + api_name = 'SYNO.DownloadStation.Task' + info = self.download_list[api_name] + api_path = info['path'] + param = {'version': info['maxVersion'], 'method': 'resume', 'id': task_id} + + if type(task_id) is list: + param['id'] = ",".join(task_id) + + return self.request_data(api_name, api_path, param) + + def edit_task(self, task_id, destination='sharedfolder'): + api_name = 'SYNO.DownloadStation.Task' + info = self.download_list[api_name] + api_path = info['path'] + param = {'version': info['maxVersion'], 'method': 'edit', 'id': task_id, 'destination': destination} + + if type(task_id) is list: + param['id'] = ",".join(task_id) + + return self.request_data(api_name, api_path, param) + + def get_statistic_info(self): + api_name = 'SYNO.DownloadStation.Statistic' + info = self.download_list[api_name] + api_path = info['path'] + param = {'version': info['maxVersion'], 'method': 'getinfo'} + + return self.request_data(api_name, api_path, param) + + def get_rss_info_list(self, offset=None, limit=None): + api_name = 'SYNO.DownloadStation.RSS.Site' + info = self.download_list[api_name] + api_path = info['path'] + param = {'version': info['maxVersion'], 'method': 'list'} + + if offset is not None: + param['offset'] = offset + if limit is not None: + param['limit'] = limit + + return self.request_data(api_name, api_path, param) + + def refresh_rss_site(self, rss_id=None): + api_name = 'SYNO.DownloadStation.RSS.Site' + info = self.download_list[api_name] + api_path = info['path'] + param = {'version': info['maxVersion'], 'method': 'refresh', 'id': rss_id} + + if rss_id is None: + return 'Enter a valid ID check if you have any with get_rss_list()' + elif type(rss_id) is list: + rss_id = ','.join(rss_id) + param['id'] = rss_id + + return self.request_data(api_name, api_path, param) + + def rss_feed_list(self, rss_id=None, offset=None, limit=None): + api_name = 'SYNO.DownloadStation.RSS.Feed' + info = self.download_list[api_name] + api_path = info['path'] + param = {'version': info['maxVersion'], 'method': 'list', 'id': rss_id} + + if rss_id is None: + return 'Enter a valid ID check if you have any with get_rss_list()' + elif type(rss_id) is list: + rss_id = ','.join(rss_id) + param['id'] = rss_id + + if offset is not None: + param['offset'] = offset + if limit is not None: + param['limit'] = limit + + return self.request_data(api_name, api_path, param) + + def start_bt_search(self, keyword=None, module='all'): + api_name = 'SYNO.DownloadStation.BTSearch' + info = self.download_list[api_name] + api_path = info['path'] + param = {'version': info['maxVersion'], 'method': 'start'} + + if keyword is None: + return 'Did you enter a keyword to search?' + else: + param['keyword'] = keyword + + param['module'] = module + + self._bt_search_id = self.request_data(api_name, api_path, param)['data']['taskid'] + + self._bt_search_id_list.append(self._bt_search_id) + + return 'You can now check the status of request with get_bt_search_results(), your id is: ' + self._bt_search_id + + def get_bt_search_results(self, taskid=None, offset=None, limit=None, sort_by=None, sort_direction=None, + filter_category=None, filter_title=None): + api_name = 'SYNO.DownloadStation.BTSearch' + info = self.download_list[api_name] + api_path = info['path'] + param = {'version': info['maxVersion'], 'method': 'list', 'taskid': taskid} + + for key, val in locals().items(): + if key not in ['self', 'api_name', 'info', 'api_path', 'param', 'taskid']: + if val is not None: + param[str(key)] = val + + if taskid is None: + return 'Enter a valid taskid, you can choose one of ' + str(self._bt_search_id_list) + elif type(taskid) is list: + param['taskid'] = ','.join(taskid) + + return self.request_data(api_name, api_path, param) + + def get_bt_search_category(self): + api_name = 'SYNO.DownloadStation.BTSearch' + info = self.download_list[api_name] + api_path = info['path'] + param = {'version': info['maxVersion'], 'method': 'get'} + + return self.request_data(api_name, api_path, param) + + def clean_bt_search(self, taskid=None): + api_name = 'SYNO.DownloadStation.BTSearch' + info = self.download_list[api_name] + api_path = info['path'] + param = {'version': info['maxVersion'], 'method': 'clean', 'taskid': taskid} + + if taskid is None: + return 'Enter a valid taskid, you can choose one of ' + str(self._bt_search_id_list) + elif type(taskid) is list: + param['taskid'] = ','.join(taskid) + for item in taskid: + self._bt_search_id_list.remove(item) + else: + self._bt_search_id_list.remove(taskid) + + return self.request_data(api_name, api_path, param) + + def get_bt_module(self): + api_name = 'SYNO.DownloadStation.BTSearch' + info = self.download_list[api_name] + api_path = info['path'] + param = {'version': info['maxVersion'], 'method': 'getModule'} + + return self.request_data(api_name, api_path, param) From 0fa2a80bf6ee08f0e58be02ce43e1b2ca60877e2 Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Tue, 21 Jan 2020 14:32:36 +1300 Subject: [PATCH 03/14] Fix Syno Parser #1671 --- core/utils/parsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/utils/parsers.py b/core/utils/parsers.py index 2fc5f0705..12303a3d1 100644 --- a/core/utils/parsers.py +++ b/core/utils/parsers.py @@ -91,7 +91,7 @@ def parse_synods(args): input_hash = os.getenv('TR_TORRENT_HASH') if not input_name: # No info passed. Assume manual download. return input_directory, input_name, input_category, input_hash, input_id - input_id = 'dbid_'.format(os.getenv('TR_TORRENT_ID')) + input_id = 'dbid_{0}'.format(os.getenv('TR_TORRENT_ID')) #res = core.TORRENT_CLASS.tasks_list(additional_param='detail') res = core.TORRENT_CLASS.tasks_info(input_id, additional_param='detail') logger.debug('result from syno {0}'.format(res)) From 11f1c2ce3ffb85c0173ce973de17406f3c932673 Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Tue, 21 Jan 2020 14:34:35 +1300 Subject: [PATCH 04/14] Update Syno Default port. #1671 --- autoProcessMedia.cfg.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoProcessMedia.cfg.spec b/autoProcessMedia.cfg.spec index bcd6b528c..b2623e538 100644 --- a/autoProcessMedia.cfg.spec +++ b/autoProcessMedia.cfg.spec @@ -388,7 +388,7 @@ qBittorrentPWD = your password ###### Synology Download Station (You must edit this if you're using TorrentToMedia.py with Synology DS) synoHost = localhost - synoPort = 9091 + synoPort = 5000 synoUSR = your username synoPWD = your password ###### ADVANCED USE - ONLY EDIT IF YOU KNOW WHAT YOU'RE DOING ###### From 2a96311d6f6c650c8e5419eeccccf56473b21281 Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Fri, 24 Jan 2020 23:05:16 +1300 Subject: [PATCH 05/14] Qbittorrent patch 1 (#1711) qBittorrenHost to qBittorrentHost (#1710) Co-authored-by: boredazfcuk --- autoProcessMedia.cfg.spec | 2 +- core/configuration.py | 3 +++ core/plugins/downloaders/torrent/configuration.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/autoProcessMedia.cfg.spec b/autoProcessMedia.cfg.spec index b2623e538..a189b1c30 100644 --- a/autoProcessMedia.cfg.spec +++ b/autoProcessMedia.cfg.spec @@ -382,7 +382,7 @@ DelugeUSR = your username DelugePWD = your password ###### qBittorrent (You must edit this if you're using TorrentToMedia.py with qBittorrent) - qBittorrenHost = localhost + qBittorrentHost = localhost qBittorrentPort = 8080 qBittorrentUSR = your username qBittorrentPWD = your password diff --git a/core/configuration.py b/core/configuration.py index 13dc8f724..134c05d58 100644 --- a/core/configuration.py +++ b/core/configuration.py @@ -191,6 +191,9 @@ def cleanup_values(values, section): if option == 'forceClean': CFG_NEW['General']['force_clean'] = value values.pop(option) + if option == 'qBittorrenHost': #We had a typo that is now fixed. + CFG_NEW['Torrent']['qBittorrentHost'] = value + values.pop(option) if section in ['Transcoder']: if option in ['niceness']: CFG_NEW['Posix'][option] = value diff --git a/core/plugins/downloaders/torrent/configuration.py b/core/plugins/downloaders/torrent/configuration.py index e89c883f5..8acd4b617 100644 --- a/core/plugins/downloaders/torrent/configuration.py +++ b/core/plugins/downloaders/torrent/configuration.py @@ -85,7 +85,7 @@ def configure_deluge(config): def configure_qbittorrent(config): - core.QBITTORRENT_HOST = config['qBittorrenHost'] # localhost + core.QBITTORRENT_HOST = config['qBittorrentHost'] # localhost core.QBITTORRENT_PORT = int(config['qBittorrentPort']) # 8080 core.QBITTORRENT_USER = config['qBittorrentUSR'] # mysecretusr core.QBITTORRENT_PASSWORD = config['qBittorrentPWD'] # mysecretpwr From c18fb17fd832d175c6e475b980136ef8afc751c8 Mon Sep 17 00:00:00 2001 From: cheese1 Date: Wed, 29 Jan 2020 00:53:52 +0100 Subject: [PATCH 06/14] fix typos (#1714) --- .github/README.md | 2 +- nzbToMedia.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/README.md b/.github/README.md index 0757be3cd..2428e3482 100644 --- a/.github/README.md +++ b/.github/README.md @@ -32,7 +32,7 @@ Installation instructions for this are available in the [wiki](https://github.co Contribution ------------ -We who have developed nzbToMedia believe in the openness of open-source, and as such we hope that any modifications will lead back to the [orignal repo](https://github.com/clinton-hall/nzbToMedia "orignal repo") via pull requests. +We who have developed nzbToMedia believe in the openness of open-source, and as such we hope that any modifications will lead back to the [original repo](https://github.com/clinton-hall/nzbToMedia "orignal repo") via pull requests. Founder: [clinton-hall](https://github.com/clinton-hall "clinton-hall") diff --git a/nzbToMedia.py b/nzbToMedia.py index 89ed8a8e0..485b22799 100755 --- a/nzbToMedia.py +++ b/nzbToMedia.py @@ -175,12 +175,12 @@ # api key for www.omdbapi.com (used as alternative to imdb to assist with movie identification). #W3omdbapikey= -# Wacther3 Delete Failed Downloads (0, 1). +# Watcher3 Delete Failed Downloads (0, 1). # # set to 1 to delete failed, or 0 to leave files in place. #W3delete_failed=0 -# Wacther3 wait_for +# Watcher3 wait_for # # Set the number of minutes to wait after calling the renamer, to check the movie has changed status. #W3wait_for=2 From a233db002473af894e03b87edc4d09e81a5fbf7e Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Mon, 2 Mar 2020 18:19:57 +1300 Subject: [PATCH 07/14] SickGear 403 fix (#1722) 403 from SickGear #1704 --- core/auto_process/tv.py | 4 ++-- core/forks.py | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/core/auto_process/tv.py b/core/auto_process/tv.py index 801f32345..6b3ba7b78 100644 --- a/core/auto_process/tv.py +++ b/core/auto_process/tv.py @@ -272,7 +272,7 @@ def process(section, dir_name, input_name=None, failed=False, client_agent='manu url = None if section == 'SickBeard': if apikey: - url = '{0}{1}:{2}{3}/api/{4}/?cmd=postprocess'.format(protocol, host, port, web_root, apikey) + url = '{0}{1}:{2}{3}/api/{4}/'.format(protocol, host, port, web_root, apikey) elif fork == 'Stheno': url = '{0}{1}:{2}{3}/home/postprocess/process_episode'.format(protocol, host, port, web_root) else: @@ -300,7 +300,7 @@ def process(section, dir_name, input_name=None, failed=False, client_agent='manu login = '{0}{1}:{2}{3}/login'.format(protocol, host, port, web_root) login_params = {'username': username, 'password': password} r = s.get(login, verify=False, timeout=(30, 60)) - if r.status_code == 401 and r.cookies.get('_xsrf'): + if r.status_code in [401, 403] and r.cookies.get('_xsrf'): login_params['_xsrf'] = r.cookies.get('_xsrf') s.post(login, data=login_params, stream=True, verify=False, timeout=(30, 60)) r = s.get(url, auth=(username, password), params=fork_params, stream=True, verify=False, timeout=(30, 1800)) diff --git a/core/forks.py b/core/forks.py index ac73c5476..19efad0e0 100644 --- a/core/forks.py +++ b/core/forks.py @@ -104,7 +104,7 @@ def auto_fork(section, input_category): # then in order of most unique parameters. if apikey: - url = '{protocol}{host}:{port}{root}/api/{apikey}/?cmd=help&subject=postprocess'.format( + url = '{protocol}{host}:{port}{root}/api/{apikey}/?cmd=sg.postprocess&help=1'.format( protocol=protocol, host=host, port=port, root=web_root, apikey=apikey, ) else: @@ -120,7 +120,7 @@ def auto_fork(section, input_category): protocol=protocol, host=host, port=port, root=web_root) login_params = {'username': username, 'password': password} r = s.get(login, verify=False, timeout=(30, 60)) - if r.status_code == 401 and r.cookies.get('_xsrf'): + if r.status_code in [401, 403] and r.cookies.get('_xsrf'): login_params['_xsrf'] = r.cookies.get('_xsrf') s.post(login, data=login_params, stream=True, verify=False) r = s.get(url, auth=(username, password), verify=False) @@ -131,8 +131,10 @@ def auto_fork(section, input_category): if r and r.ok: if apikey: rem_params, found = api_check(r, params, rem_params) - if not found: # try different api set for SickGear. - url = '{protocol}{host}:{port}{root}/api/{apikey}/?cmd=postprocess&help=1'.format( + if found: + params['cmd'] = 'sg.postprocess' + else: # try different api set for non-SickGear forks. + url = '{protocol}{host}:{port}{root}/api/{apikey}/?cmd=help&subject=postprocess'.format( protocol=protocol, host=host, port=port, root=web_root, apikey=apikey, ) try: @@ -141,6 +143,7 @@ def auto_fork(section, input_category): logger.info('Could not connect to {section}:{category} to perform auto-fork detection!'.format (section=section, category=input_category)) rem_params, found = api_check(r, params, rem_params) + params['cmd'] = 'postprocess' else: # Find excess parameters rem_params.extend( From 4facc36e3f5dfa8a5088313ba8031db7eee9ba0c Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Mon, 2 Mar 2020 21:38:10 +1300 Subject: [PATCH 08/14] fix return for incorrect command. --- core/forks.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/forks.py b/core/forks.py index 19efad0e0..25cb6d5dc 100644 --- a/core/forks.py +++ b/core/forks.py @@ -21,6 +21,9 @@ def api_check(r, params, rem_params): logger.debug('Response received') raise + if isinstance(json_data, str): + return rem_params, False + try: json_data = json_data['data'] except KeyError: From f8de0c1ccfb2d4fe2a40539cc807d5340cc0ec04 Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Mon, 2 Mar 2020 21:56:59 +1300 Subject: [PATCH 09/14] fix api check --- core/forks.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/forks.py b/core/forks.py index 25cb6d5dc..c1cbb91c4 100644 --- a/core/forks.py +++ b/core/forks.py @@ -21,9 +21,6 @@ def api_check(r, params, rem_params): logger.debug('Response received') raise - if isinstance(json_data, str): - return rem_params, False - try: json_data = json_data['data'] except KeyError: @@ -31,6 +28,8 @@ def api_check(r, params, rem_params): logger.debug('Response received: {}'.format(json_data)) raise else: + if isinstance(json_data, str): + return rem_params, False json_data = json_data.get('data', json_data) try: From c037387fc3c2fe7e2a4cd713ec3d27367585cc0c Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Tue, 3 Mar 2020 12:34:02 +1300 Subject: [PATCH 10/14] always use cmd type for api. #1723 --- core/auto_process/tv.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/auto_process/tv.py b/core/auto_process/tv.py index 6b3ba7b78..b6c4f21de 100644 --- a/core/auto_process/tv.py +++ b/core/auto_process/tv.py @@ -273,6 +273,11 @@ def process(section, dir_name, input_name=None, failed=False, client_agent='manu if section == 'SickBeard': if apikey: url = '{0}{1}:{2}{3}/api/{4}/'.format(protocol, host, port, web_root, apikey) + if not 'cmd' in fork_params: + if 'SickGear' in fork: + fork_params['cmd'] = 'sg.postprocess' + else: + fork_params['cmd'] = 'postprocess' elif fork == 'Stheno': url = '{0}{1}:{2}{3}/home/postprocess/process_episode'.format(protocol, host, port, web_root) else: From 58a6b2022b6f798951efc77d563018555b0ef535 Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Sun, 8 Mar 2020 13:35:21 +1300 Subject: [PATCH 11/14] Fix dictionary changed size. #1724 (#1726) --- core/auto_process/movies.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/auto_process/movies.py b/core/auto_process/movies.py index 6535cddd7..32e93595c 100644 --- a/core/auto_process/movies.py +++ b/core/auto_process/movies.py @@ -548,21 +548,27 @@ def get_release(base_url, imdb_id=None, download_id=None, release_id=None): # Narrow results by removing old releases by comparing their last_edit field if len(results) > 1: + rem_id = [] for id1, x1 in results.items(): for x2 in results.values(): try: if x2['last_edit'] > x1['last_edit']: - results.pop(id1) + rem_id.append(id1) except Exception: continue + for id in rem_id: + results.pop(id) # Search downloads on clients for a match to try and narrow our results down to 1 if len(results) > 1: + rem_id = [] for cur_id, x in results.items(): try: if not find_download(str(x['download_info']['downloader']).lower(), x['download_info']['id']): - results.pop(cur_id) + rem_id.append(cur_id) except Exception: continue + for id in rem_id: + results.pop(id) return results From 001f754cd36f401fd47cd17d53515524db730519 Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Sun, 8 Mar 2020 13:36:26 +1300 Subject: [PATCH 12/14] Fix unicode check in Py2 #1725 (#1727) --- core/forks.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/forks.py b/core/forks.py index c1cbb91c4..021631eef 100644 --- a/core/forks.py +++ b/core/forks.py @@ -28,7 +28,11 @@ def api_check(r, params, rem_params): logger.debug('Response received: {}'.format(json_data)) raise else: - if isinstance(json_data, str): + if six.PY3: + str_type = (str) + else: + str_type = (str, unicode) + if isinstance(json_data, str_type): return rem_params, False json_data = json_data.get('data', json_data) From b409279254525b58168f4c86e598bbca75639c78 Mon Sep 17 00:00:00 2001 From: Clinton Hall Date: Mon, 9 Mar 2020 06:55:13 +1300 Subject: [PATCH 13/14] fix py2 handling #1725 --- core/forks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/core/forks.py b/core/forks.py index 021631eef..b9fe4edd3 100644 --- a/core/forks.py +++ b/core/forks.py @@ -8,6 +8,7 @@ ) import requests +import six from six import iteritems import core From 5fb3229c137fa4598d40e1a5187820942aaef5c9 Mon Sep 17 00:00:00 2001 From: clinton-hall Date: Fri, 17 Apr 2020 11:26:57 +1200 Subject: [PATCH 14/14] update to version 12.1.05 --- .bumpversion.cfg | 2 +- core/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index aa40e7a1a..4a55bc33b 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 12.1.04 +current_version = 12.1.05 commit = True tag = False diff --git a/core/__init__.py b/core/__init__.py index 7198ab94a..3cdc3c9b8 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -83,7 +83,7 @@ wake_up, ) -__version__ = '12.1.04' +__version__ = '12.1.05' # Client Agents NZB_CLIENTS = ['sabnzbd', 'nzbget', 'manual'] diff --git a/setup.py b/setup.py index df77e2622..6cb9d5dc9 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ def read(*names, **kwargs): setup( name='nzbToMedia', - version='12.1.04', + version='12.1.05', license='GPLv3', description='Efficient on demand post processing', long_description="""