IMP: Update all qBittorrent WebAPI paths for client v4.1.0+. IMP: New functions to add torrents to qBittorrent by URL and file. IMP: Remove variable to distinguish between old and new WebAPI in qBittorrent

This commit is contained in:
Arathen 2019-02-13 00:46:12 +11:00 committed by evilhero
parent 954369f7b0
commit 96851510a5
4 changed files with 87 additions and 103 deletions

View File

@ -704,21 +704,17 @@
<small>Folder path where torrents will be assigned to</small>
</div>
<div class="row left">
<input type="radio" name="qbittorrent_loadaction" id="qbittorrent_clientdefault" value="0" ${config['qbittorrent_clientdefault']}>Add torrent using client default action
<input type="radio" name="qbittorrent_loadaction" id="qbittorrent_clientdefault" value="0" ${config['qbittorrent_clientdefault']}>Add torrent only
</div>
<div class="row left">
<input type="radio" name="qbittorrent_loadaction" id="qbittorrent_forcestart" value="1" ${config['qbittorrent_forcestart']}>Add torrent and force start
</div>
<div class="row left">
<input type="radio" name="qbittorrent_loadaction" id="qbittorrent_addpaused" value="2" ${config['qbittorrent_addpaused']}>Add torrent paused
<input type="radio" name="qbittorrent_loadaction" id="qbittorrent_addpaused" value="2" ${config['qbittorrent_addpaused']}>Add torrent and pause
</div>
<div class="row checkbox left clearfix">
<small>qBittorrent "Add torrent" options</small>
</div>
<div class="row checkbox left clearfix">
<input id="qbittorrent_newapi" type="checkbox" name="qbittorrent_newapi" value="1" ${config['qbittorrent_newapi']} /><label>qBittorrent version >4.1.0? (Use new WebAPI)</label>
<small>Use the new qBittorrent client WebAPI available since v4.1.0</small>
</div>
</fieldset>
</div>
</td>

View File

@ -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)

View File

@ -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),

View File

@ -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',