#!/usr/bin/env python3.6 """Custom delugeRPC client Usage: deluge_cli add MAGNET [DIR] [--debug | --warning | --error] deluge_cli search NAME deluge_cli get TORRENT deluge_cli ls [--downloading | --seeding | --paused] deluge_cli toggle TORRENT deluge_cli rm TORRENT [--debug | --warning | --error] deluge_cli (-h | --help) deluge_cli --version Arguments: MAGNET Magnet link to add DIR Directory to save to TORRENT A selected torrent Options: -h --help Show this screen --version Show version --debug Print all debug log --warning Print only logged warnings --error Print error messages (Error/Warning) """ import argparse import os import re import signal import socket import logging import logging.config import configparser from distutils.util import strtobool from pprint import pprint from deluge_client import DelugeRPCClient from sshtunnel import SSHTunnelForwarder from docopt import docopt from utils import ColorizeFilter, convert BASE_DIR = os.path.dirname(os.path.abspath(__file__)) logger = logging.getLogger('deluge_cli') logger.setLevel(logging.DEBUG) fh = logging.FileHandler('deluge_cli.log') fh.setLevel(logging.DEBUG) ch = logging.StreamHandler() ch.setLevel(logging.ERROR) formatter = logging.Formatter('%(asctime)s %(levelname)8s %(name)s | %(message)s') fh.setFormatter(formatter) logger.addHandler(fh) logger.addHandler(ch) logger.addFilter(ColorizeFilter()) def split_words(string): logger.debug('Splitting input: {} (type: {}) with split_words'.format(string, type(string))) return re.findall(r"[\w\d']+", string.lower()) class Deluge(object): """docstring for ClassName""" def __init__(self, config=None): self.host = config['Deluge']['HOST'] self.port = int(config['Deluge']['PORT']) self.user = config['Deluge']['USER'] self.password = config['Deluge']['PASSWORD'] self.ssh_host = config['ssh']['HOST'] self.ssh_user = config['ssh']['USER'] self.ssh_pkey = config['ssh']['PKEY'] self._connect() def parseResponse(self, response): torrents = [] for key in response: torrent = response[key] torrents.append(Torrent.fromDeluge(torrent)) return torrents def _connect(self): logger.info('Checking if script on same server as deluge RPC') if (socket.gethostbyname(socket.gethostname()) != self.host): self.tunnel = SSHTunnelForwarder(self.ssh_host, ssh_username=self.ssh_user, ssh_pkey=self.ssh_pkey, local_bind_address=('localhost', self.port), remote_bind_address=('localhost', self.port)) self.tunnel.start() self.client = DelugeRPCClient(self.host, self.port, self.user, self.password) self.client.connect() def add(self, url): if (url.startswith('magnet')): return self.client.call('core.add_torrent_magnet', url, {}) def ls(self, _filter=None): if (type(_filter) is list and len(_filter)): if ('seeding' in _filter): response = self.client.call('core.get_torrents_status', {'state': 'Seeding'}, []) elif ('downloading' in _filter): response = self.client.call('core.get_torrents_status', {'state': 'Downloading'}, []) elif ('paused' in _filter): response = self.client.call('core.get_torrents_status', {'paused': 'true'}, []) else: response = self.client.call('core.get_torrents_status', {}, []) return self.parseResponse(response) def search(self, query): q_list = split_words(query) return [ t for t in self.ls() if (set(q_list) <= set(split_words(t.name))) ] def get(self, id): response = self.client.call('core.get_torrent_status', id, {}) return Torrent.fromDeluge(response) def togglePaused(self, id): torrent = self.get(id) if (torrent.paused): response = self.client.call('core.resume_torrent', [id]) else: response = self.client.call('core.pause_torrent', [id]) print('Response:', response) def remove(self, name): for torrent in self.ls(): if (name == torrent.name): response = self.client.call('core.remove_torrent', torrent.id, False) logger.info('Response: ', response) break if (response == False): raise AttributeError('Unable to remove torrent.') def status(self): response = self.client.call('core.get_torrents_status', {}, ['progress']) torrents = self.parseResponse(response) def __del__(self): if hasattr(self, 'tunnel'): logger.info('Closing ssh tunnel') self.tunnel.stop() class Torrent(object): def __init__(self, key, name, progress, eta, save_path, state, paused, finished, files): super(Torrent, self).__init__() self.key = key self.name = name self.progress = "{0:.2f}".format(float(progress)) self.eta = eta self.save_path = save_path self.state = state self.paused = paused self.finished = finished self.files = list(files) def packed(self): return len(self.files) > 1 @classmethod def fromDeluge(cls, d): # Receive a dict with byte values, convert all elements to string values d = convert(d) d['paused'] = True if strtobool(d['paused']) else False return cls(d['hash'], d['name'], d['progress'], d['eta'], d['save_path'], d['state'], d['paused'], d['is_finished'], d['files']) def toJSON(self): return {'Key': self.key, 'Name': self.name, 'Progress': self.progress, 'ETA': self.eta, 'Save path': self.save_path, 'State': self.state, 'Paused': self.paused, 'Finished': self.finished, 'Files': self.files, 'Packed': self.packed()} def __str__(self): return "Name: {}, Progress: {}%, ETA: {}, State: {}, Paused: {}".format( self.name, self.progress, self.eta, self.state, self.paused) def getConfig(): """ Read path and get configuartion file with site settings :return: config settings read from 'config.ini' :rtype: configparser.ConfigParser """ config = configparser.ConfigParser() config_dir = os.path.join(BASE_DIR, 'config.ini') config.read(config_dir) config_values = list(dict(config.items('Deluge')).values()) config_values.extend(list(dict(config.items('ssh')).values())) if any(value.startswith('YOUR') for value in config_values): raise ValueError('Please set variables in config.ini file.') return config def signal_handler(signal, frame): """ Handle exit by Keyboardinterrupt """ logger.info('\nGood bye!') sys.exit(0) def main(): """ Main function, parse the input """ signal.signal(signal.SIGINT, signal_handler) arguments = docopt(__doc__, version='1') # Set logging level for streamHandler if arguments['--debug']: ch.setLevel(logging.DEBUG) elif arguments['--warning']: ch.setLevel(logging.WARNING) elif arguments['--error']: ch.setLevel(logging.ERROR) logger.info('Deluge client') logger.debug(arguments) # Get config settings config_settings = getConfig() deluge = Deluge(config=config_settings) _id = arguments['TORRENT'] query = arguments['NAME'] magnet = arguments['MAGNET'] _filter = [ a[2:] for a in ['--downloading', '--seeding', '--paused'] if arguments[a] ] print(_id, query, _filter) if arguments['add']: logger.info('Add cmd selected with link {}'.format(magnet)) response = deluge.add(magnet) print('Add response: ', response) elif arguments['search']: logger.info('Search cmd selected for query: {}'.format(query)) response = deluge.search(query) [ pprint(t.toJSON()) for t in response ] elif arguments['get']: logger.info('Get cmd selected for id: {}'.format(_id)) response = deluge.get(_id) pprint(response.toJSON()) elif arguments['ls']: logger.info('List cmd selected') [ pprint(t.toJSON()) for t in deluge.ls(_filter=_filter) ] elif arguments['toggle']: logger.info('Toggling id: {}'.format(_id)) deluge.togglePaused(_id) elif arguments['rm']: logger.info('Remove id: {}'.format(_id)) deluge.remove(_id) if __name__ == '__main__': main()