Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/qbittorrent api v2 #7040

Merged
merged 11 commits into from
Aug 13, 2019
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Converted the footer to a Vue component ([#4520](https://github.com/pymedusa/Medusa/pull/4520))
- Converted Edit Show to a Vue SFC ([#4486](https://github.com/pymedusa/Medusa/pull/4486)
- Improved API v2 exception reporting on Python 2 ([#6931](https://github.com/pymedusa/Medusa/pull/6931))
- Added support for qbittorrent api v2. Required from qbittorrent version > 3.2.0. ([#7040](https://github.com/pymedusa/Medusa/pull/7040))

#### Fixes
- Fixed hdtorrent provider parse the publishing date with the day first ([#6847](https://github.com/pymedusa/Medusa/pull/6847))
Expand Down
124 changes: 93 additions & 31 deletions medusa/clients/torrent/qbittorrent.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,73 @@ def __init__(self, host=None, username=None, password=None):
@property
def api(self):
"""Get API version."""
# Update the auth method to v2
self._get_auth = self._get_auth_v2
# Attempt to get API v2 version first
self.url = '{host}api/v2/app/webapiVersion'.format(host=self.host)
try:
version = self.session.get(self.url, verify=app.TORRENT_VERIFY_CERT,
cookies=self.session.cookies)
# Make sure version is using the (major, minor, release) format
version = list(map(int, version.text.split('.')))
if len(version) < 2:
version.append(0)
return tuple(version)
except (AttributeError, ValueError) as error:
log.error('{name}: Unable to get API version. Error: {error!r}',
{'name': self.name, 'error': error})

# Fall back to API v1
self._get_auth = self._get_auth_legacy
try:
self.url = '{host}version/api'.format(host=self.host)
version = int(self.session.get(self.url, verify=app.TORRENT_VERIFY_CERT).content)
# Convert old API versioning to new versioning (major, minor, release)
version = (1, version % 100, 0)
except Exception:
version = 1
version = (1, 0, 0)
return version

def _get_auth(self):
"""Select between api v2 and legacy."""
return self._get_auth_v2() or self._get_auth_legacy()

if self.api > 1:
self.url = '{host}login'.format(host=self.host)
data = {
'username': self.username,
'password': self.password,
}
try:
self.response = self.session.post(self.url, data=data)
except Exception:
return None
def _get_auth_v2(self):
"""Authenticate using the new method (API v2)."""
self.url = '{host}api/v2/auth/login'.format(host=self.host)
data = {
'username': self.username,
'password': self.password,
}
try:
self.response = self.session.post(self.url, data=data)
except Exception:
return None

else:
if self.response.status_code == 404:
return None

self.session.cookies = self.response.cookies
self.auth = self.response.content

return self.auth

def _get_auth_legacy(self):
"""Authenticate using the legacy method (API v1)."""
self.url = '{host}login'.format(host=self.host)
data = {
'username': self.username,
'password': self.password,
}
try:
self.response = self.session.post(self.url, data=data)
except Exception:
return None

# Pre-API v1
if self.response.status_code == 404:
try:
self.response = self.session.get(self.host, verify=app.TORRENT_VERIFY_CERT)
self.auth = self.response.content
except Exception:
return None

Expand All @@ -70,41 +113,54 @@ def _get_auth(self):

def _add_torrent_uri(self, result):

self.url = '{host}command/download'.format(host=self.host)
command = 'api/v2/torrents/add' if self.api >= (2, 0, 0) else 'command/download'
self.url = '{host}{command}'.format(host=self.host, command=command)
data = {
'urls': result.url,
}
return self._request(method='post', data=data, cookies=self.session.cookies)

def _add_torrent_file(self, result):

self.url = '{host}command/upload'.format(host=self.host)
command = 'api/v2/torrents/add' if self.api >= (2, 0, 0) else 'command/upload'
self.url = '{host}{command}'.format(host=self.host, command=command)
files = {
'torrents': result.content
'torrents': (
'{result}.torrent'.format(result=result.name),
result.content,
),
}
return self._request(method='post', files=files, cookies=self.session.cookies)

def _set_torrent_label(self, result):

label = app.TORRENT_LABEL_ANIME if result.series.is_anime else app.TORRENT_LABEL

if self.api > 6 and label:
label_key = 'Category' if self.api >= 10 else 'Label'
if not label:
return True

api = self.api
if api >= (2, 0, 0):
self.url = '{host}api/v2/torrents/setCategory'.format(host=self.host)
label_key = 'category'
elif api > (1, 6, 0):
label_key = 'Category' if api >= (1, 10, 0) else 'Label'
self.url = '{host}command/set{key}'.format(
host=self.host,
key=label_key,
)
data = {
'hashes': result.hash.lower(),
label_key.lower(): label.replace(' ', '_'),
}
return self._request(method='post', data=data, cookies=self.session.cookies)
return True

data = {
'hashes': result.hash.lower(),
label_key.lower(): label.replace(' ', '_'),
}
return self._request(method='post', data=data, cookies=self.session.cookies)

def _set_torrent_priority(self, result):

self.url = '{host}command/{method}Prio'.format(host=self.host,
method='increase' if result.priority == 1 else 'decrease')
command = 'api/v2/torrents' if self.api >= (2, 0, 0) else 'command'
method = 'increase' if result.priority == 1 else 'decrease'
self.url = '{host}{command}/{method}Prio'.format(
host=self.host, command=command, method=method)
data = {
'hashes': result.hash.lower(),
}
Expand All @@ -118,9 +174,11 @@ def _set_torrent_priority(self, result):
return ok

def _set_torrent_pause(self, result):
self.url = '{host}command/{state}'.format(host=self.host,
state='pause' if app.TORRENT_PAUSED else 'resume')
hashes_key = 'hashes' if self.api >= 18 else 'hash'
api = self.api
state = 'pause' if app.TORRENT_PAUSED else 'resume'
command = 'api/v2/torrents' if api >= (2, 0, 0) else 'command'
hashes_key = 'hashes' if self.api >= (1, 18, 0) else 'hash'
self.url = '{host}{command}/{state}'.format(host=self.host, command=command, state=state)
data = {
hashes_key: result.hash.lower(),
}
Expand All @@ -134,10 +192,14 @@ def remove_torrent(self, info_hash):
:return
:rtype: bool
"""
self.url = '{host}command/deletePerm'.format(host=self.host)
data = {
'hashes': info_hash.lower(),
}
if self.api >= (2, 0, 0):
self.url = '{host}api/v2/torrents/delete'.format(host=self.host)
data['deleteFiles'] = True
else:
self.url = '{host}command/deletePerm'.format(host=self.host)

return self._request(method='post', data=data, cookies=self.session.cookies)

Expand Down