diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html
index 0d301f41..d80baab9 100755
--- a/data/interfaces/default/config.html
+++ b/data/interfaces/default/config.html
@@ -704,21 +704,17 @@
Folder path where torrents will be assigned to
- Add torrent using client default action
+ Add torrent only
Add torrent and force start
- Add torrent paused
+ Add torrent and pause
qBittorrent "Add torrent" options
-
-
- Use the new qBittorrent client WebAPI available since v4.1.0
-
diff --git a/lib/qbittorrent/client.py b/lib/qbittorrent/client.py
index 73d8d753..6809144d 100644
--- a/lib/qbittorrent/client.py
+++ b/lib/qbittorrent/client.py
@@ -1,7 +1,6 @@
import requests
import json
-
class LoginRequired(Exception):
def __str__(self):
return 'Please login first.'
@@ -15,7 +14,7 @@ class Client(object):
self.url = url
session = requests.Session()
- check_prefs = session.get(url+'query/preferences')
+ check_prefs = session.get(url+'api/v2/app/preferences')
if check_prefs.status_code == 200:
self._is_authenticated = True
@@ -24,9 +23,9 @@ class Client(object):
elif check_prefs.status_code == 404:
self._is_authenticated = False
raise RuntimeError("""
- This wrapper only supports qBittorrent applications
- with version higher than 3.1.x.
- Please use the latest qBittorrent release.
+ This wrapper only supports qBittorrent applications with
+ version higher than 4.1.0 (which implemented Web API v2.0).
+ Please use the latest qBittorrent release.
""")
else:
@@ -104,7 +103,7 @@ class Client(object):
:return: Response to login request to the API.
"""
self.session = requests.Session()
- login = self.session.post(self.url+'login',
+ login = self.session.post(self.url+'api/v2/auth/login',
data={'username': username,
'password': password})
if login.text == 'Ok.':
@@ -116,7 +115,7 @@ class Client(object):
"""
Logout the current session.
"""
- response = self._get('logout')
+ response = self._get('api/v2/auth/logout')
self._is_authenticated = False
return response
@@ -125,27 +124,20 @@ class Client(object):
"""
Get qBittorrent version.
"""
- return self._get('version/qbittorrent')
+ return self._get('api/v2/app/version')
@property
def api_version(self):
"""
Get WEB API version.
"""
- return self._get('version/api')
-
- @property
- def api_min_version(self):
- """
- Get minimum WEB API version.
- """
- return self._get('version/api_min')
+ return self._get('api/v2/app/webapiVersion')
def shutdown(self):
"""
Shutdown qBittorrent.
"""
- return self._get('command/shutdown')
+ return self._get('api/v2/app/shutdown')
def torrents(self, **filters):
"""
@@ -157,6 +149,7 @@ class Client(object):
:param reverse: Enable reverse sorting.
:param limit: Limit the number of torrents returned.
:param offset: Set offset (if less than 0, offset from end).
+ :param hashes: Filter by hashes. Can contain multiple hashes separated by |.
:return: list() of torrent with matching filter.
"""
@@ -166,7 +159,7 @@ class Client(object):
name = 'filter' if name == 'status' else name
params[name] = value
- return self._get('query/torrents', params=params)
+ return self._get('api/v2/torrents/info', params=params)
def get_torrent(self, infohash):
"""
@@ -174,7 +167,7 @@ class Client(object):
:param infohash: INFO HASH of the torrent.
"""
- return self._get('query/propertiesGeneral/' + infohash.lower())
+ return self._get('api/v2/torrents/properties', params={'hash': infohash.lower()})
def get_torrent_trackers(self, infohash):
"""
@@ -182,7 +175,7 @@ class Client(object):
:param infohash: INFO HASH of the torrent.
"""
- return self._get('query/propertiesTrackers/' + infohash.lower())
+ return self._get('api/v2/torrents/trackers', params={'hash': infohash.lower()})
def get_torrent_webseeds(self, infohash):
"""
@@ -190,7 +183,7 @@ class Client(object):
:param infohash: INFO HASH of the torrent.
"""
- return self._get('query/propertiesWebSeeds/' + infohash.lower())
+ return self._get('api/v2/torrents/webseeds', params={'hash': infohash.lower()})
def get_torrent_files(self, infohash):
"""
@@ -198,14 +191,14 @@ class Client(object):
:param infohash: INFO HASH of the torrent.
"""
- return self._get('query/propertiesFiles/' + infohash.lower())
+ return self._get('api/v2/torrents/files', params={'hash': infohash.lower()})
@property
def global_transfer_info(self):
"""
Get JSON data of the global transfer info of qBittorrent.
"""
- return self._get('query/transferInfo')
+ return self._get('api/v2/transfer/info')
@property
def preferences(self):
@@ -228,7 +221,7 @@ class Client(object):
qb.preferences()
"""
- prefs = self._get('query/preferences')
+ prefs = self._get('api/v2/app/preferences')
class Proxy(Client):
"""
@@ -270,11 +263,11 @@ class Client(object):
def sync(self, rid=0):
"""
Sync the torrents by supplied LAST RESPONSE ID.
- Read more @ http://git.io/vEgXr
+ Read more @ https://git.io/fxgB8
:param rid: Response ID of last request.
"""
- return self._get('sync/maindata', params={'rid': rid})
+ return self._get('api/v2/sync/maindata', params={'rid': rid})
def download_from_link(self, link, **kwargs):
"""
@@ -286,22 +279,20 @@ class Client(object):
:return: Empty JSON data.
"""
- # old:new format
- old_arg_map = {'save_path': 'savepath'} # , 'label': 'category'}
-
- # convert old option names to new option names
- options = kwargs.copy()
- for old_arg, new_arg in old_arg_map.items():
- if options.get(old_arg) and not options.get(new_arg):
- options[new_arg] = options[old_arg]
-
- options['urls'] = link
-
- # workaround to send multipart/formdata request
- # http://stackoverflow.com/a/23131823/4726598
- dummy_file = {'_dummy': (None, '_dummy')}
-
- return self._post('command/download', data=options, files=dummy_file)
+ # qBittorrent requires adds to be done with multipath/form-data
+ # POST requests for both URLs and .torrent files. Info on this
+ # can be found here, and here:
+ # http://docs.python-requests.org/en/master/user/quickstart/#post-a-multipart-encoded-file
+ # http://docs.python-requests.org/en/master/user/advanced/#post-multiple-multipart-encoded-files
+ if isinstance(link, list):
+ links = '\n'.join(link)
+ else:
+ links = link
+ torrent_data = {}
+ torrent_data['urls'] = (None, links)
+ for k, v in kwargs.iteritems():
+ torrent_data[k] = (None, v)
+ return self._post('api/v2/torrents/add', data=None, files=torrent_data)
def download_from_file(self, file_buffer, **kwargs):
"""
@@ -313,18 +304,23 @@ class Client(object):
:return: Empty JSON data.
"""
+ # qBittorrent requires adds to be done with multipath/form-data
+ # POST requests for both URLs and .torrent files. Info on this
+ # can be found here, and here:
+ # http://docs.python-requests.org/en/master/user/quickstart/#post-a-multipart-encoded-file
+ # http://docs.python-requests.org/en/master/user/advanced/#post-multiple-multipart-encoded-files
if isinstance(file_buffer, list):
- torrent_files = {}
- for i, f in enumerate(file_buffer):
- torrent_files.update({'torrents%s' % i: f})
+ torrent_data = []
+ for f in file_buffer:
+ fname = f.name
+ torrent_data.append(('torrents', (fname, f)))
else:
- torrent_files = {'torrents': file_buffer}
+ fname = file_buffer.name
+ torrent_data = [('torrents', (fname, file_buffer))]
+ for k, v in kwargs.iteritems():
+ torrent_data.append((k, (None, v)))
- data = kwargs.copy()
-
- if data.get('save_path'):
- data.update({'savepath': data['save_path']})
- return self._post('command/upload', data=data, files=torrent_files)
+ return self._post('api/v2/torrents/add', data=None, files=torrent_data)
def add_trackers(self, infohash, trackers):
"""
@@ -335,7 +331,7 @@ class Client(object):
"""
data = {'hash': infohash.lower(),
'urls': trackers}
- return self._post('command/addTrackers', data=data)
+ return self._post('api/v2/torrents/addTrackers', data=data)
@staticmethod
def _process_infohash_list(infohash_list):
@@ -356,13 +352,13 @@ class Client(object):
:param infohash: INFO HASH of torrent.
"""
- return self._post('command/pause', data={'hash': infohash.lower()})
+ return self._post('api/v2/torrents/pause', data={'hashes': infohash.lower()})
def pause_all(self):
"""
Pause all torrents.
"""
- return self._get('command/pauseAll')
+ return self._post('api/v2/torrents/pause', data={'hashes': 'all'})
def pause_multiple(self, infohash_list):
"""
@@ -371,18 +367,7 @@ class Client(object):
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
- return self._post('command/pauseAll', data=data)
-
- def set_label(self, infohash_list, label):
- """
- Set the label on multiple torrents.
- IMPORTANT: OLD API method, kept as it is to avoid breaking stuffs.
-
- :param infohash_list: Single or list() of infohashes.
- """
- data = self._process_infohash_list(infohash_list)
- data['label'] = label
- return self._post('command/setLabel', data=data)
+ return self._post('api/v2/torrents/pause', data=data)
def set_category(self, infohash_list, category):
"""
@@ -392,7 +377,7 @@ class Client(object):
"""
data = self._process_infohash_list(infohash_list)
data['category'] = category
- return self._post('command/setCategory', data=data)
+ return self._post('api/v2/torrents/setCategory', data=data)
def resume(self, infohash):
"""
@@ -400,13 +385,13 @@ class Client(object):
:param infohash: INFO HASH of torrent.
"""
- return self._post('command/resume', data={'hash': infohash.lower()})
+ return self._post('api/v2/torrents/resume', data={'hashes': infohash.lower()})
def resume_all(self):
"""
Resume all torrents.
"""
- return self._get('command/resumeAll')
+ return self._get('api/v2/torrents/resume', data={'hashes': 'all'})
def resume_multiple(self, infohash_list):
"""
@@ -415,7 +400,7 @@ class Client(object):
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
- return self._post('command/resumeAll', data=data)
+ return self._post('api/v2/torrents/resume', data=data)
def delete(self, infohash_list):
"""
@@ -424,16 +409,21 @@ class Client(object):
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
- return self._post('command/delete', data=data)
+ data['deleteFiles'] = 'false'
+ return self._post('api/v2/torrents/delete', data=data)
def delete_permanently(self, infohash_list):
"""
Permanently delete torrents.
+ *** WARNING : This will instruct qBittorrent to delete files
+ *** from your hard disk. Use with caution.
+
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
- return self._post('command/deletePerm', data=data)
+ data['deleteFiles'] = 'true'
+ return self._post('api/v2/torrents/delete', data=data)
def recheck(self, infohash_list):
"""
@@ -442,7 +432,7 @@ class Client(object):
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
- return self._post('command/recheck', data=data)
+ return self._post('api/v2/torrents/recheck', data=data)
def increase_priority(self, infohash_list):
"""
@@ -451,7 +441,7 @@ class Client(object):
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
- return self._post('command/increasePrio', data=data)
+ return self._post('api/v2/torrents/increasePrio', data=data)
def decrease_priority(self, infohash_list):
"""
@@ -460,7 +450,7 @@ class Client(object):
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
- return self._post('command/decreasePrio', data=data)
+ return self._post('api/v2/torrents/decreasePrio', data=data)
def set_max_priority(self, infohash_list):
"""
@@ -469,7 +459,7 @@ class Client(object):
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
- return self._post('command/topPrio', data=data)
+ return self._post('api/v2/torrents/topPrio', data=data)
def set_min_priority(self, infohash_list):
"""
@@ -478,7 +468,7 @@ class Client(object):
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
- return self._post('command/bottomPrio', data=data)
+ return self._post('api/v2/torrents/bottomPrio', data=data)
def set_file_priority(self, infohash, file_id, priority):
"""
@@ -488,7 +478,7 @@ class Client(object):
:param file_id: ID of the file to set priority.
:param priority: Priority level of the file.
"""
- if priority not in [0, 1, 2, 7]:
+ if priority not in [0, 1, 6, 7]:
raise ValueError("Invalid priority, refer WEB-UI docs for info.")
elif not isinstance(file_id, int):
raise TypeError("File ID must be an int")
@@ -497,7 +487,7 @@ class Client(object):
'id': file_id,
'priority': priority}
- return self._post('command/setFilePrio', data=data)
+ return self._post('api/v2/torrents/filePrio', data=data)
# Get-set global download and upload speed limits.
@@ -505,7 +495,7 @@ class Client(object):
"""
Get global download speed limit.
"""
- return self._get('command/getGlobalDlLimit')
+ return self._get('api/v2/transfer/downloadLimit')
def set_global_download_limit(self, limit):
"""
@@ -513,7 +503,7 @@ class Client(object):
:param limit: Speed limit in bytes.
"""
- return self._post('command/setGlobalDlLimit', data={'limit': limit})
+ return self._post('api/v2/transfer/setDownloadLimit', data={'limit': limit})
global_download_limit = property(get_global_download_limit,
set_global_download_limit)
@@ -522,7 +512,7 @@ class Client(object):
"""
Get global upload speed limit.
"""
- return self._get('command/getGlobalUpLimit')
+ return self._get('api/v2/transfer/uploadLimit')
def set_global_upload_limit(self, limit):
"""
@@ -530,7 +520,7 @@ class Client(object):
:param limit: Speed limit in bytes.
"""
- return self._post('command/setGlobalUpLimit', data={'limit': limit})
+ return self._post('api/v2/transfer/setUploadLimit', data={'limit': limit})
global_upload_limit = property(get_global_upload_limit,
set_global_upload_limit)
@@ -543,7 +533,7 @@ class Client(object):
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
- return self._post('command/getTorrentsDlLimit', data=data)
+ return self._post('api/v2/torrents/downloadLimit', data=data)
def set_torrent_download_limit(self, infohash_list, limit):
"""
@@ -554,7 +544,7 @@ class Client(object):
"""
data = self._process_infohash_list(infohash_list)
data.update({'limit': limit})
- return self._post('command/setTorrentsDlLimit', data=data)
+ return self._post('api/v2/torrents/setDownloadLimit', data=data)
def get_torrent_upload_limit(self, infohash_list):
"""
@@ -563,7 +553,7 @@ class Client(object):
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
- return self._post('command/getTorrentsUpLimit', data=data)
+ return self._post('api/v2/torrents/uploadLimit', data=data)
def set_torrent_upload_limit(self, infohash_list, limit):
"""
@@ -574,26 +564,26 @@ class Client(object):
"""
data = self._process_infohash_list(infohash_list)
data.update({'limit': limit})
- return self._post('command/setTorrentsUpLimit', data=data)
+ return self._post('api/v2/torrents/setUploadLimit', data=data)
# setting preferences
def set_preferences(self, **kwargs):
"""
Set preferences of qBittorrent.
- Read all possible preferences @ http://git.io/vEgDQ
+ Read all possible preferences @ https://git.io/fx2Y9
:param kwargs: set preferences in kwargs form.
"""
json_data = "json={}".format(json.dumps(kwargs))
headers = {'content-type': 'application/x-www-form-urlencoded'}
- return self._post('command/setPreferences', data=json_data,
+ return self._post('api/v2/app/setPreferences', data=json_data,
headers=headers)
def get_alternative_speed_status(self):
"""
Get Alternative speed limits. (1/0)
"""
- return self._get('command/alternativeSpeedLimitsEnabled')
+ return self._get('api/v2/transfer/speedLimitsMode')
alternative_speed_status = property(get_alternative_speed_status)
@@ -601,7 +591,7 @@ class Client(object):
"""
Toggle alternative speed limits.
"""
- return self._get('command/toggleAlternativeSpeedLimits')
+ return self._get('api/v2/transfer/toggleSpeedLimitsMode')
def toggle_sequential_download(self, infohash_list):
"""
@@ -610,7 +600,7 @@ class Client(object):
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
- return self._post('command/toggleSequentialDownload', data=data)
+ return self._post('api/v2/torrents/toggleSequentialDownload', data=data)
def toggle_first_last_piece_priority(self, infohash_list):
"""
@@ -619,7 +609,7 @@ class Client(object):
:param infohash_list: Single or list() of infohashes.
"""
data = self._process_infohash_list(infohash_list)
- return self._post('command/toggleFirstLastPiecePrio', data=data)
+ return self._post('api/v2/torrents/toggleFirstLastPiecePrio', data=data)
def force_start(self, infohash_list, value=True):
"""
@@ -630,4 +620,4 @@ class Client(object):
"""
data = self._process_infohash_list(infohash_list)
data.update({'value': json.dumps(value)})
- return self._post('command/setForceStart', data=data)
+ return self._post('api/v2/torrents/setForceStart', data=data)
diff --git a/mylar/config.py b/mylar/config.py
index 1e73ec27..069110bc 100644
--- a/mylar/config.py
+++ b/mylar/config.py
@@ -349,7 +349,6 @@ _CONFIG_DEFINITIONS = OrderedDict({
'QBITTORRENT_LABEL': (str, 'qBittorrent', None),
'QBITTORRENT_FOLDER': (str, 'qBittorrent', None),
'QBITTORRENT_LOADACTION': (int, 'qBittorrent', 0), #0': client default, #1': force start, #2': add paused
- 'QBITTORRENT_NEWAPI': (bool, 'qBittorrent', True),
'OPDS_ENABLE': (bool, 'OPDS', False),
'OPDS_AUTHENTICATION': (bool, 'OPDS', False),
diff --git a/mylar/webserve.py b/mylar/webserve.py
index d3f36fbc..9abccf5e 100644
--- a/mylar/webserve.py
+++ b/mylar/webserve.py
@@ -4742,7 +4742,6 @@ class WebInterface(object):
"qbittorrent_clientdefault": helpers.radio(mylar.CONFIG.QBITTORRENT_LOADACTION, 0),
"qbittorrent_forcestart": helpers.radio(mylar.CONFIG.QBITTORRENT_LOADACTION, 1),
"qbittorrent_addpaused": helpers.radio(mylar.CONFIG.QBITTORRENT_LOADACTION, 2),
- "qbittorrent_newapi": helpers.checked(mylar.CONFIG.QBITTORRENT_NEWAPI),
"blackhole_dir": mylar.CONFIG.BLACKHOLE_DIR,
"usenet_retention": mylar.CONFIG.USENET_RETENTION,
"nzbsu": helpers.checked(mylar.CONFIG.NZBSU),
@@ -5107,7 +5106,7 @@ class WebInterface(object):
def configUpdate(self, **kwargs):
checked_configs = ['enable_https', 'launch_browser', 'syno_fix', 'auto_update', 'annuals_on', 'api_enabled', 'nzb_startup_search',
'enforce_perms', 'sab_to_mylar', 'torrent_local', 'torrent_seedbox', 'rtorrent_ssl', 'rtorrent_verify', 'rtorrent_startonload',
- 'enable_torrents', 'qbittorrent_newapi', 'enable_rss', 'nzbsu', 'nzbsu_verify',
+ 'enable_torrents', 'enable_rss', 'nzbsu', 'nzbsu_verify',
'dognzb', 'dognzb_verify', 'experimental', 'enable_torrent_search', 'enable_public', 'enable_32p', 'enable_torznab',
'newznab', 'use_minsize', 'use_maxsize', 'ddump', 'failed_download_handling', 'sab_client_post_processing', 'nzbget_client_post_processing',
'failed_auto', 'post_processing', 'enable_check_folder', 'enable_pre_scripts', 'enable_snatch_script', 'enable_extra_scripts',