1
0
mirror of https://github.com/moparisthebest/SickRage synced 2024-11-05 17:05:03 -05:00

Improved rTorrent support.

Now use requests library.
Added SSL support with Basic and Digest support.
This commit is contained in:
Alexandre Beloin 2015-01-29 23:53:29 -05:00
parent fb3cb22808
commit 3166f29d34
7 changed files with 279 additions and 22 deletions

View File

@ -466,12 +466,29 @@
</label>
</div>
<div class="field-pair" id="torrent_auth_type">
<label>
<span class="component-title">Http Authentication</span>
<span class="component-desc">
<select name="torrent_auth_type" id="torrent_auth_type" class="form-control input-sm">
#set $http_authtype = {'none': "None", 'basic': "Basic", 'digest': "Digest"}
#for $authvalue,$authname in $http_authtype.items():
#set $selected = $html_selected if $sickbeard.TORRENT_AUTH_TYPE == $authvalue else ''
<option value="$authvalue"$selected>$authname</option>
#end for
</select>
<p></p>
</span>
</label>
</div>
<div class="field-pair" id="torrent_verify_cert_option">
<label for="torrent_verify_cert">
<span class="component-title">Verify certificate</span>
<span class="component-desc">
<input type="checkbox" name="torrent_verify_cert" class="enabler" id="torrent_verify_cert" <%= html_checked if sickbeard.TORRENT_VERIFY_CERT == True else '' %>/>
<p>disable if you get "Deluge: Authentication Error" in your log</p>
<p id="torrent_verify_deluge">disable if you get "Deluge: Authentication Error" in your log</p>
<p id="torrent_verify_rtorrent">Verify SSL certificates for HTTPS requests</p>
</span>
</label>
</div>

View File

@ -69,6 +69,9 @@ $(document).ready(function(){
$(host_desc_rtorrent).hide();
$(host_desc_torrent).show();
$(torrent_verify_cert_option).hide();
$(torrent_verify_deluge).hide();
$(torrent_verify_rtorrent).hide();
$(torrent_auth_type).hide();
$(torrent_path_option).show();
$(torrent_path_option).find('.fileBrowser').show();
$(torrent_seed_time_option).hide();
@ -96,6 +99,8 @@ $(document).ready(function(){
} else if ('deluge' == selectedProvider){
client = 'Deluge';
$(torrent_verify_cert_option).show();
$(torrent_verify_deluge).show();
$(torrent_verify_rtorrent).hide();
$(label_warning_deluge).show();
$(label_anime_warning_deluge).show();
$('#host_desc_torrent').text('URL to your Deluge client (e.g. http://localhost:8112)');
@ -113,6 +118,10 @@ $(document).ready(function(){
client = 'rTorrent';
$(torrent_paused_option).hide();
$('#host_desc_torrent').text('URL to your rTorrent client (e.g. scgi://localhost:5000 </br> or https://localhost/rutorrent/plugins/httprpc/action.php)');
$(torrent_verify_cert_option).show();
$(torrent_verify_deluge).hide();
$(torrent_verify_rtorrent).show();
$(torrent_auth_type).show();
//$('#directory_title').text(client + directory);
}
$('#host_title').text(client + host);

View File

@ -22,15 +22,16 @@ import os.path
import time
import xmlrpclib
from rtorrent.common import find_torrent, \
is_valid_port, convert_version_tuple_to_str
from rtorrent.lib.torrentparser import TorrentParser
from rtorrent.lib.xmlrpc.http import HTTPServerProxy
from rtorrent.lib.xmlrpc.scgi import SCGIServerProxy
from rtorrent.rpc import Method
from rtorrent.lib.xmlrpc.basic_auth import BasicAuthTransport
from rtorrent.torrent import Torrent
from rtorrent.group import Group
from rtorrent.common import (find_torrent, # @UnresolvedImport
is_valid_port, # @UnresolvedImport
convert_version_tuple_to_str) # @UnresolvedImport
from rtorrent.lib.torrentparser import TorrentParser # @UnresolvedImport
from rtorrent.lib.xmlrpc.http import HTTPServerProxy # @UnresolvedImport
from rtorrent.lib.xmlrpc.scgi import SCGIServerProxy # @UnresolvedImport
from rtorrent.rpc import Method # @UnresolvedImport
from rtorrent.lib.xmlrpc.requests_transport import RequestsTransport # @UnresolvedImport @IgnorePep8
from rtorrent.torrent import Torrent # @UnresolvedImport
from rtorrent.group import Group # @UnresolvedImport
import rtorrent.rpc # @UnresolvedImport
__version__ = "0.2.9"
@ -43,11 +44,12 @@ MIN_RTORRENT_VERSION_STR = convert_version_tuple_to_str(MIN_RTORRENT_VERSION)
class RTorrent:
""" Create a new rTorrent connection """
rpc_prefix = None
def __init__(self, uri, username=None, password=None,
verify=False, sp=None, sp_kwargs=None):
verify=False, sp=None, sp_kwargs=None, tp_kwargs=None):
self.uri = uri # : From X{__init__(self, url)}
self.username = username
@ -59,6 +61,10 @@ class RTorrent:
self.sp = sp
elif self.schema in ['http', 'https']:
self.sp = HTTPServerProxy
if self.schema == 'https':
self.isHttps = True
else:
self.isHttps = False
elif self.schema == 'scgi':
self.sp = SCGIServerProxy
else:
@ -66,6 +72,8 @@ class RTorrent:
self.sp_kwargs = sp_kwargs or {}
self.tp_kwargs = tp_kwargs or {}
self.torrents = [] # : List of L{Torrent} instances
self._rpc_methods = [] # : List of rTorrent RPC methods
self._torrent_cache = []
@ -80,9 +88,30 @@ class RTorrent:
if self.schema == 'scgi':
raise NotImplementedError()
if 'authtype' not in self.tp_kwargs:
authtype = None
else:
authtype = self.tp_kwargs['authtype']
if 'check_ssl_cert' not in self.tp_kwargs:
check_ssl_cert = True
else:
check_ssl_cert = self.tp_kwargs['check_ssl_cert']
if 'proxies' not in self.tp_kwargs:
proxies = None
else:
proxies = self.tp_kwargs['proxies']
return self.sp(
self.uri,
transport=BasicAuthTransport(self.username, self.password),
transport=RequestsTransport(
use_https=self.isHttps,
authtype=authtype,
username=self.username,
password=self.password,
check_ssl_cert=check_ssl_cert,
proxies=proxies),
**self.sp_kwargs
)
@ -90,8 +119,10 @@ class RTorrent:
def _verify_conn(self):
# check for rpc methods that should be available
assert "system.client_version" in self._get_rpc_methods(), "Required RPC method not available."
assert "system.library_version" in self._get_rpc_methods(), "Required RPC method not available."
assert "system.client_version" in self._get_rpc_methods(
), "Required RPC method not available."
assert "system.library_version" in self._get_rpc_methods(
), "Required RPC method not available."
# minimum rTorrent version check
assert self._meets_version_requirement() is True,\
@ -152,7 +183,8 @@ class RTorrent:
for result in results:
results_dict = {}
# build results_dict
for m, r in zip(retriever_methods, result[1:]): # result[0] is the info_hash
# result[0] is the info_hash
for m, r in zip(retriever_methods, result[1:]):
results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
self.torrents.append(
@ -199,7 +231,7 @@ class RTorrent:
return(func_name)
def load_magnet(self, magneturl, info_hash, start=False, verbose=False, verify_load=True):
def load_magnet(self, magneturl, info_hash, start=False, verbose=False, verify_load=True): # @IgnorePep8
p = self._get_conn()
@ -231,13 +263,13 @@ class RTorrent:
while i < MAX_RETRIES:
for torrent in self.get_torrents():
if torrent.info_hash == info_hash:
if str(info_hash) not in str(torrent.name) :
if str(info_hash) not in str(torrent.name):
time.sleep(1)
i += 1
return(torrent)
def load_torrent(self, torrent, start=False, verbose=False, verify_load=True):
def load_torrent(self, torrent, start=False, verbose=False, verify_load=True): # @IgnorePep8
"""
Loads torrent into rTorrent (with various enhancements)
@ -354,7 +386,7 @@ class RTorrent:
if persistent is True:
p.group.insert_persistent_view('', name)
else:
assert view is not None, "view parameter required on non-persistent groups"
assert view is not None, "view parameter required on non-persistent groups" # @IgnorePep8
p.group.insert('', name, view)
self._update_rpc_methods()

View File

@ -0,0 +1,188 @@
# Copyright (c) 2013-2015 Alexandre Beloin, <alexandre.beloin@gmail.com>
#
# 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 3 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 <http://www.gnu.org/licenses/>.
"""A transport for Python2/3 xmlrpc library using requests
Support:
-SSL with Basic and Digest authentication
-Proxies
"""
try:
import xmlrpc.client as xmlrpc_client
except ImportError:
import xmlrpclib as xmlrpc_client
import traceback
import requests
from requests.exceptions import RequestException
from requests.auth import HTTPBasicAuth
from requests.auth import HTTPDigestAuth
from requests.packages.urllib3 import disable_warnings # @UnresolvedImport
class RequestsTransport(xmlrpc_client.Transport):
"""Transport class for xmlrpc using requests"""
def __init__(self, use_https=True, authtype=None, username=None,
password=None, check_ssl_cert=True, proxies=None):
"""Inits RequestsTransport.
Args:
use_https: If true, https else http
authtype: None, basic or digest
username: Username
password: Password
check_ssl_cert: Check SSL certificate
proxies: A dict of proxies(
Ex: {"http": "http://10.10.1.10:3128",
"https": "http://10.10.1.10:1080",})
Raises:
ValueError: Invalid info
"""
# Python 2 can't use super on old style class.
if issubclass(xmlrpc_client.Transport, object):
super(RequestsTransport, self).__init__()
else:
xmlrpc_client.Transport.__init__(self)
self.user_agent = "Python Requests/" + requests.__version__
self._use_https = use_https
self._check_ssl_cert = check_ssl_cert
if authtype == "basic" or authtype == "digest":
self._authtype = authtype
else:
raise ValueError(
"Supported authentication are: basic and digest")
if authtype and (not username or not password):
raise ValueError(
"Username and password required when using authentication")
self._username = username
self._password = password
if proxies is None:
self._proxies = {}
else:
self._proxies = proxies
def request(self, host, handler, request_body, verbose=0):
"""Replace the xmlrpc request function.
Process xmlrpc request via requests library.
Args:
host: Target host
handler: Target PRC handler.
request_body: XML-RPC request body.
verbose: Debugging flag.
Returns:
Parsed response.
Raises:
RequestException: Error in requests
"""
if verbose:
self._debug()
if not self._check_ssl_cert:
disable_warnings()
headers = {'User-Agent': self.user_agent, 'Content-Type': 'text/xml', }
# Need to be done because the schema(http or https) is lost in
# xmlrpc.Transport's init.
if self._use_https:
url = "https://{host}/{handler}".format(host=host, handler=handler)
else:
url = "http://{host}/{handler}".format(host=host, handler=handler)
# TODO Construct kwargs query instead
try:
if self._authtype == "basic":
response = requests.post(
url,
data=request_body,
headers=headers,
verify=self._check_ssl_cert,
auth=HTTPBasicAuth(
self._username, self._password),
proxies=self._proxies)
elif self._authtype == "digest":
response = requests.post(
url,
data=request_body,
headers=headers,
verify=self._check_ssl_cert,
auth=HTTPDigestAuth(
self._username, self._password),
proxies=self._proxies)
else:
response = requests.post(
url,
data=request_body,
headers=headers,
verify=self._check_ssl_cert,
proxies=self._proxies)
response.raise_for_status()
except RequestException as error:
raise xmlrpc_client.ProtocolError(url,
error.message,
traceback.format_exc(),
response.headers)
return self.parse_response(response)
def parse_response(self, response):
"""Replace the xmlrpc parse_response function.
Parse response.
Args:
response: Requests return data
Returns:
Response tuple and target method.
"""
p, u = self.getparser()
p.feed(response.text)
p.close()
return u.close()
def _debug(self):
"""Debug requests module.
Enable verbose logging from requests
"""
# TODO Ugly
import logging
try:
import http.client as http_client
except ImportError:
import httplib as http_client
http_client.HTTPConnection.debuglevel = 1
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True

View File

@ -288,6 +288,7 @@ TORRENT_LABEL = ''
TORRENT_LABEL_ANIME = ''
TORRENT_VERIFY_CERT = False
TORRENT_RPCURL = 'transmission'
TORRENT_AUTH_TYPE = 'none'
USE_KODI = False
KODI_ALWAYS_ON = True
@ -503,7 +504,7 @@ def initialize(consoleLogging=True):
HANDLE_REVERSE_PROXY, USE_NZBS, USE_TORRENTS, NZB_METHOD, NZB_DIR, DOWNLOAD_PROPERS, RANDOMIZE_PROVIDERS, CHECK_PROPERS_INTERVAL, ALLOW_HIGH_PRIORITY, TORRENT_METHOD, \
SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, SAB_CATEGORY_ANIME, SAB_HOST, \
NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_CATEGORY_ANIME, NZBGET_PRIORITY, NZBGET_HOST, NZBGET_USE_HTTPS, backlogSearchScheduler, \
TORRENT_USERNAME, TORRENT_PASSWORD, TORRENT_HOST, TORRENT_PATH, TORRENT_SEED_TIME, TORRENT_PAUSED, TORRENT_HIGH_BANDWIDTH, TORRENT_LABEL, TORRENT_LABEL_ANIME, TORRENT_VERIFY_CERT, TORRENT_RPCURL, \
TORRENT_USERNAME, TORRENT_PASSWORD, TORRENT_HOST, TORRENT_PATH, TORRENT_SEED_TIME, TORRENT_PAUSED, TORRENT_HIGH_BANDWIDTH, TORRENT_LABEL, TORRENT_LABEL_ANIME, TORRENT_VERIFY_CERT, TORRENT_RPCURL, TORRENT_AUTH_TYPE, \
USE_KODI, KODI_ALWAYS_ON, KODI_NOTIFY_ONSNATCH, KODI_NOTIFY_ONDOWNLOAD, KODI_NOTIFY_ONSUBTITLEDOWNLOAD, KODI_UPDATE_FULL, KODI_UPDATE_ONLYFIRST, \
KODI_UPDATE_LIBRARY, KODI_HOST, KODI_USERNAME, KODI_PASSWORD, BACKLOG_FREQUENCY, \
USE_TRAKT, TRAKT_USERNAME, TRAKT_PASSWORD, TRAKT_REMOVE_WATCHLIST, TRAKT_USE_WATCHLIST, TRAKT_METHOD_ADD, TRAKT_START_PAUSED, traktCheckerScheduler, TRAKT_USE_RECOMMENDED, TRAKT_SYNC, TRAKT_DEFAULT_INDEXER, TRAKT_REMOVE_SERIESLIST, TRAKT_DISABLE_SSL_VERIFY, TRAKT_TIMEOUT, \
@ -816,6 +817,7 @@ def initialize(consoleLogging=True):
TORRENT_LABEL_ANIME = check_setting_str(CFG, 'TORRENT', 'torrent_label_anime', '')
TORRENT_VERIFY_CERT = bool(check_setting_int(CFG, 'TORRENT', 'torrent_verify_cert', 0))
TORRENT_RPCURL = check_setting_str(CFG, 'TORRENT', 'torrent_rpcurl', 'transmission')
TORRENT_AUTH_TYPE = check_setting_str(CFG, 'TORRENT', 'torrent_auth_type', '')
USE_KODI = bool(check_setting_int(CFG, 'KODI', 'use_kodi', 0))
KODI_ALWAYS_ON = bool(check_setting_int(CFG, 'KODI', 'kodi_always_on', 1))
@ -1681,6 +1683,7 @@ def save_config():
new_config['TORRENT']['torrent_label_anime'] = TORRENT_LABEL_ANIME
new_config['TORRENT']['torrent_verify_cert'] = int(TORRENT_VERIFY_CERT)
new_config['TORRENT']['torrent_rpcurl'] = TORRENT_RPCURL
new_config['TORRENT']['torrent_auth_type'] = TORRENT_AUTH_TYPE
new_config['KODI'] = {}
new_config['KODI']['use_kodi'] = int(USE_KODI)

View File

@ -37,8 +37,15 @@ class rTorrentAPI(GenericClient):
if not self.host:
return
tp_kwargs = {}
if sickbeard.TORRENT_AUTH_TYPE is not 'none':
tp_kwargs['authtype'] = sickbeard.TORRENT_AUTH_TYPE
if not sickbeard.TORRENT_VERIFY_CERT:
tp_kwargs['check_ssl_cert'] = False
if self.username and self.password:
self.auth = RTorrent(self.host, self.username, self.password)
self.auth = RTorrent(self.host, self.username, self.password, True, tp_kwargs=tp_kwargs)
else:
self.auth = RTorrent(self.host, None, None, True)

View File

@ -3656,7 +3656,7 @@ class ConfigSearch(Config):
torrent_dir=None, torrent_username=None, torrent_password=None, torrent_host=None,
torrent_label=None, torrent_label_anime=None, torrent_path=None, torrent_verify_cert=None,
torrent_seed_time=None, torrent_paused=None, torrent_high_bandwidth=None,
torrent_rpcurl=None, ignore_words=None, require_words=None):
torrent_rpcurl=None, torrent_auth_type = None, ignore_words=None, require_words=None):
results = []
@ -3717,6 +3717,7 @@ class ConfigSearch(Config):
sickbeard.TORRENT_HIGH_BANDWIDTH = config.checkbox_to_value(torrent_high_bandwidth)
sickbeard.TORRENT_HOST = config.clean_url(torrent_host)
sickbeard.TORRENT_RPCURL = torrent_rpcurl
sickbeard.TORRENT_AUTH_TYPE = torrent_auth_type
sickbeard.save_config()