From a0b538861c434363f74f3f0181eeaff70595a6fd Mon Sep 17 00:00:00 2001 From: rembo10 Date: Sat, 27 Jul 2013 14:19:26 +0530 Subject: [PATCH] Transmission API working --- data/interfaces/default/config.html | 4 +- headphones/searcher.py | 23 ++++--- headphones/transmission.py | 76 +++++++++++++++++++++++ headphones/utorrent.py | 94 +++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+), 12 deletions(-) diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 48db074c..7d558b1a 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -198,7 +198,7 @@
- +
@@ -337,7 +337,7 @@
Torrents
- +
diff --git a/headphones/searcher.py b/headphones/searcher.py index 6efbc0be..07a2190f 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -33,6 +33,7 @@ import string import headphones, exceptions from headphones import logger, db, helpers, classes, sab, nzbget +from headphones import transmission import lib.bencode as bencode @@ -473,7 +474,7 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): for result in resultlist: if high_size_limit and (result[1] > high_size_limit): - logger.info(result[0] + " is too large for this album - not considering it. (Size: " + helpers.bytes_to_mb(result[1]) + ", Maxsize: " + helpers.bytes_to_mb(high_size_limit)) + logger.info(result[0] + " is too large for this album - not considering it. (Size: " + helpers.bytes_to_mb(result[1]) + ", Maxsize: " + helpers.bytes_to_mb(high_size_limit) + ")") # Add lossless nzbs to the "flac list" which we can use if there are no good lossy matches if 'flac' in result[0].lower(): @@ -482,7 +483,7 @@ def searchNZB(albumid=None, new=False, losslessOnly=False): continue if low_size_limit and (result[1] < low_size_limit): - logger.info(result[0] + " is too small for this album - not considering it. (Size: " + helpers.bytes_to_mb(result[1]) + ", Minsize: " + helpers.bytes_to_mb(low_size_limit)) + logger.info(result[0] + " is too small for this album - not considering it. (Size: " + helpers.bytes_to_mb(result[1]) + ", Minsize: " + helpers.bytes_to_mb(low_size_limit) + ")") continue delta = abs(targetsize - result[1]) @@ -1351,14 +1352,9 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): if data and bestqual: logger.info(u'Found best result from %s: %s - %s' % (bestqual[3], bestqual[2], bestqual[0], helpers.bytes_to_mb(bestqual[1]))) torrent_folder_name = '%s - %s [%s]' % (helpers.latinToAscii(albums[0]).encode('UTF-8').replace('/', '_'), helpers.latinToAscii(albums[1]).encode('UTF-8').replace('/', '_'), year) - if headphones.TORRENTBLACKHOLE_DIR == "sendtracker": - torrent = classes.TorrentDataSearchResult() - torrent.extraInfo.append(data) - torrent.name = torrent_folder_name - sab.sendTorrent(torrent) - - elif headphones.TORRENTBLACKHOLE_DIR != "": + # Blackhole + if headphones.TORRENT_DOWNLOADER == 0: # Get torrent name from .torrent, this is usually used by the torrent client as the folder name @@ -1388,6 +1384,11 @@ def searchTorrent(albumid=None, new=False, losslessOnly=False): logger.error('Couldn\'t get name from Torrent file: %s' % e) break + elif headphones.TORRENT_DOWNLOADER == 1: + logger.info("Sending torrent to Transmission") + transmission.sendTorrent(bestqual[2]) + + myDB.action('UPDATE albums SET status = "Snatched" WHERE AlbumID=?', [albums[2]]) myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?, ?, ?)', [albums[2], bestqual[0], bestqual[1], bestqual[2], "Snatched", torrent_folder_name, "torrent"]) @@ -1399,9 +1400,11 @@ def preprocesstorrent(resultlist, pre_sorted_list=False): elif int(selresult[1]) < int(result[1]): # if size is lower than new result replace previous selected result (bigger size = better quality?) selresult = result - # get outta here if rutracker or piratebay + # get outta here if rutracker or piratebay, or if we're using Transmission or uTorrent if selresult[3] == 'rutracker.org' or selresult[3] == 'The Pirate Bay': return True, selresult + if headphones.TORRENT_DOWNLOADER != 0: + return True, selresult if pre_sorted_list: selresult = resultlist[0] diff --git a/headphones/transmission.py b/headphones/transmission.py index e3b93d4e..21d7e584 100644 --- a/headphones/transmission.py +++ b/headphones/transmission.py @@ -13,4 +13,80 @@ # You should have received a copy of the GNU General Public License # along with Headphones. If not, see . +import headphones +from headphones import logger, notifiers +import urllib2 +import lib.simplejson as json +import base64 + +# This is just a simple script to send torrents to transmission. The +# intention is to turn this into a class where we can check the state +# of the download, set the download dir, etc. +# TODO: Store the session id so we don't need to make 2 calls +# Store torrent id so we can check up on it + +def sendTorrent(link): + + host = headphones.TRANSMISSION_HOST + username = headphones.TRANSMISSION_USERNAME + password = headphones.TRANSMISSION_PASSWORD + sessionid = None + + if not host.startswith('http'): + host = 'http://' + host + + if host.endswith('/'): + host = host[:-1] + + host = host + "/transmission/rpc" + request = urllib2.Request(host) + if username and password: + base64string = base64.encodestring('%s:%s' % (username, password)).replace('\n', '') + request.add_header("Authorization", "Basic %s" % base64string) + opener = urllib2.build_opener() + try: + data = opener.open(request).read() + except urllib2.HTTPError, e: + if e.code == 409: + sessionid = e.hdrs['x-transmission-session-id'] + else: + logger.error('Could not connect to Transmission. Error: ' + str(e)) + except Exception, e: + logger.error('Could not connect to Transmission. Error: ' + str(e)) + + if not sessionid: + logger.error("Error getting Session ID from Transmission") + return + + request.add_header('x-transmission-session-id', sessionid) + + postdata = json.dumps({ 'method': 'torrent-add', + 'arguments': { 'filename': link, + 'download-dir': headphones.DOWNLOAD_TORRENT_DIR } }) + + request.add_data(postdata) + + try: + response = json.loads(opener.open(request).read()) + except Exception, e: + logger.error("Error sending torrent to Transmission: " + str(e)) + return + + if response['result'] == 'success': + name = response['arguments']['torrent-added']['name'] + logger.info(u"Torrent sent to Transmission successfully") + if headphones.PROWL_ENABLED and headphones.PROWL_ONSNATCH: + logger.info(u"Sending Prowl notification") + prowl = notifiers.PROWL() + prowl.notify(name,"Download started") + if headphones.PUSHOVER_ENABLED and headphones.PUSHOVER_ONSNATCH: + logger.info(u"Sending Pushover notification") + prowl = notifiers.PUSHOVER() + prowl.notify(name,"Download started") + if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH: + logger.debug(u"Sending NMA notification") + nma = notifiers.NMA() + nma.notify(snatched_nzb=name) + + return True diff --git a/headphones/utorrent.py b/headphones/utorrent.py index e3b93d4e..e464495b 100644 --- a/headphones/utorrent.py +++ b/headphones/utorrent.py @@ -13,4 +13,98 @@ # You should have received a copy of the GNU General Public License # along with Headphones. If not, see . +## uTorrentAPI class taken from CouchPotatoServer +## http://github.com/RuudBurger/CouchPotatoServer +class uTorrentAPI(object): + + def __init__(self, host = 'localhost', port = 8000, username = None, password = None): + + super(uTorrentAPI, self).__init__() + + self.url = 'http://' + str(host) + ':' + str(port) + '/gui/' + self.token = '' + self.last_time = time.time() + cookies = cookielib.CookieJar() + self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), MultipartPostHandler) + self.opener.addheaders = [('User-agent', 'couchpotato-utorrent-client/1.0')] + if username and password: + password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() + password_manager.add_password(realm = None, uri = self.url, user = username, passwd = password) + self.opener.add_handler(urllib2.HTTPBasicAuthHandler(password_manager)) + self.opener.add_handler(urllib2.HTTPDigestAuthHandler(password_manager)) + elif username or password: + log.debug('User or password missing, not using authentication.') + self.token = self.get_token() + + def _request(self, action, data = None): + if time.time() > self.last_time + 1800: + self.last_time = time.time() + self.token = self.get_token() + request = urllib2.Request(self.url + "?token=" + self.token + "&" + action, data) + try: + open_request = self.opener.open(request) + response = open_request.read() + if response: + return response + else: + log.debug('Unknown failure sending command to uTorrent. Return text is: %s', response) + except httplib.InvalidURL, err: + log.error('Invalid uTorrent host, check your config %s', err) + except urllib2.HTTPError, err: + if err.code == 401: + log.error('Invalid uTorrent Username or Password, check your config') + else: + log.error('uTorrent HTTPError: %s', err) + except urllib2.URLError, err: + log.error('Unable to connect to uTorrent %s', err) + return False + + def get_token(self): + request = self.opener.open(self.url + "token.html") + token = re.findall("(.*?)