class Users(object): def __init__(self): self.logger = logging.getLogger('modules.users') Manageusers.createTable(ifNotExists=True) htpc.MODULES.append({ 'name': 'Manage users', 'description': '<div class="alert alert-block alert-danger"><i class="fa fa-exclamation-triangle fa-fw"></i> Make sure you enable authentication and provide a master username and password in General settings, otherwise authentication will not be used.</div>', 'isThirdParty': False, 'id': 'users', 'action': htpc.WEBDIR + 'users/setusers', 'fields': [ {'type': 'select', 'label': 'User', 'name': 'users_user_id', 'options': [{'name': 'New', 'value': 0}] }, {'type': 'text', 'label': 'Username', 'name': 'users_user_username'}, {'type': 'password', 'label': 'Password', 'name': 'users_user_password'}, {'type': 'select', 'label': 'Role', 'name': 'users_user_role', 'desc': 'Admin users can change settings whilst normal users can only view pages.', 'options': [ {'name': 'restricted user', 'value': 'restricted_user'}, {'name': 'user', 'value': 'user'}, {'name': 'admin', 'value': 'admin'} ] } ] }) @cherrypy.expose() @require(member_of(htpc.role_admin)) def setusers(self, users_user_id, users_user_username, users_user_password, users_user_role): if users_user_id == "0": self.logger.debug('Creating Manage users in db') try: Manageusers(username=users_user_username, password=users_user_password, role=users_user_role) htpc.BLACKLISTWORDS.append(users_user_password) return 'hack' except Exception, e: self.logger.debug('Failed to create %s %s' % (users_user_username, e)) return else:
class Users: def __init__(self): self.logger = logging.getLogger('modules.users') Manageusers.createTable(ifNotExists=True) htpc.MODULES.append({ 'name': 'Manage users', 'description': 'Add more users to HTPC-Manager. Make sure you enable authentication and have provided a master username and password in General settings, otherwise authentication will not be used.', 'isThirdParty': False, 'id': 'users', 'action': htpc.WEBDIR + 'users/setusers', 'fields': [{ 'type': 'select', 'label': 'User', 'name': 'users_user_id', 'options': [{ 'name': 'New', 'value': 0 }] }, { 'type': 'text', 'label': 'Username', 'name': 'users_user_username' }, { 'type': 'password', 'label': 'Password', 'name': 'users_user_password' }, { 'type': 'select', 'label': 'Role', 'name': 'users_user_role', 'desc': 'Admin users can change settings while normal users can only view pages.', 'options': [{ 'name': 'user', 'value': 'user' }, { 'name': 'admin', 'value': 'admin' }] }] }) @cherrypy.expose() @require(member_of("admin")) def index(self): return htpc.LOOKUP.get_template('manageusers.html').render( scriptname='manageusers') @cherrypy.expose() @require(member_of("admin")) def setusers(self, users_user_id, users_user_username, users_user_password, users_user_role): if users_user_id == "0": self.logger.debug('Creating Manage users in db') try: Manageusers(username=users_user_username, password=users_user_password, role=users_user_role) return 'hack' except Exception, e: self.logger.debug('Failed to create %s %s' % (users_user_username, e)) return else:
return else: try: users = Manageusers.selectBy( username=users_user_username).getOne() users.username = users_user_username users.password = users_user_password users.role = users_user_role return 'hack' except SQLObjectNotFound, e: self.logger.debug('Failed to update username on %s' % users_user_username) return @cherrypy.expose() @require(member_of("admin")) @cherrypy.tools.json_out() def getuser(self, id=None): if id: """ Get user info, used by settings """ try: user = Manageusers.selectBy(id=id).getOne() return dict( (c, getattr(user, c)) for c in user.sqlmeta.columns) except SQLObjectNotFound: return """ Get a list of all users""" users = [] for s in Manageusers.select(): users.append({'id': s.id, 'name': s.username}) if len(users) < 1:
class Mylar(object): def __init__(self): self.logger = logging.getLogger('modules.mylar') htpc.MODULES.append({ 'name': 'Mylar', 'id': 'mylar', 'test': htpc.WEBDIR + 'mylar/ping', 'fields': [{ 'type': 'bool', 'label': 'Enable', 'name': 'mylar_enable' }, { 'type': 'text', 'label': 'Menu name', 'name': 'mylar_name' }, { 'type': 'text', 'label': 'IP / Host *', 'name': 'mylar_host' }, { 'type': 'text', 'label': 'Port *', 'name': 'mylar_port' }, { 'type': 'text', 'label': 'Basepath', 'name': 'mylar_basepath' }, { 'type': 'text', 'label': 'API key', 'name': 'mylar_apikey' }, { 'type': 'bool', 'label': 'Use SSL', 'name': 'mylar_ssl' }, { "type": "text", "label": "Reverse proxy link", "placeholder": "", "desc": "Reverse proxy link ex: https://hp.domain.com", "name": "mylar_reverse_proxy_link" }] }) @cherrypy.expose() @require() def index(self): template = htpc.LOOKUP.get_template('mylar.html') settings = htpc.settings return template.render(scriptname='mylar', url=Mylar.webinterface(), name=settings.get('mylar_name', 'mylar')) @cherrypy.expose() @require() def GetThumb(self, url=None, thumb=None, h=None, w=None, o=100): """ Parse thumb to get the url and send to htpc.proxy.get_image """ self.logger.debug("Trying to fetch image via %s", url) if url is None and thumb is None: # To stop if the image is missing return # Should never used thumb, to lazy to remove it if thumb: url = thumb return get_image(url, h, w, o) @cherrypy.expose() @require() def viewcomic(self, artist_id): response = self.fetch('getComic&id=%s' % artist_id) for a in response['comic']: a['StatusText'] = _get_status_icon(a['Status']) a['can_download'] = True if a['Status'] not in ( 'Downloaded', 'Snatched', 'Wanted') else False template = htpc.LOOKUP.get_template('mylar_view_comic.html') return template.render(scriptname='mylar_view_comic', comic_id=artist_id, comic=response['comic'][0], comicimg=response['comic'][0]['ComicImageURL'], issues=response['issues'], description=response['comic'][0]['Description'], module_name=htpc.settings.get( 'mylar_name', 'Mylar')) @staticmethod def _build_url(ssl=None, host=None, port=None, base_path=None): ssl = ssl or htpc.settings.get('mylar_ssl') host = host or htpc.settings.get('mylar_host') port = port or htpc.settings.get('mylar_port') path = fix_basepath(htpc.settings.get('mylar_basepath', '/')) url = '{protocol}://{host}:{port}{path}'.format( protocol='https' if ssl else 'http', host=host, port=port, path=path, ) return url @staticmethod def webinterface(): url = Mylar._build_url() if htpc.settings.get('mylar_reverse_proxy_link'): url = htpc.settings.get('mylar_reverse_proxy_link') return url @staticmethod def _build_api_url(command, url=None, api_key=None): return '{url}api?apikey={api_key}&cmd={command}'.format( url=url or Mylar._build_url(), api_key=api_key or htpc.settings.get('mylar_apikey'), command=command, ) @cherrypy.expose() @cherrypy.tools.json_out() @require() def getserieslist(self): return self.fetch('getIndex') @cherrypy.expose() @cherrypy.tools.json_out() @require() def GetWantedList(self): return self.fetch('getWanted') @cherrypy.expose() @cherrypy.tools.json_out() @require() def SearchForComic(self, name): return self.fetch('findComic&%s' % urlencode( {'name': name.encode(encoding='UTF-8', errors='strict')})) @cherrypy.expose() @require() def RefreshComic(self, Id): return self.fetch('refreshComic&id=%s' % Id, text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def DeleteComic(self, Id): return self.fetch('delComic&id=%s' % Id, text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def PauseComic(self, Id): return self.fetch('pauseComic&id=%s' % Id, text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def ResumeComic(self, Id): return self.fetch('resumeComic&id=%s' % Id, text=True) @cherrypy.expose() @require() def QueueIssue(self, issueid=None, new=False, **kwargs): # Force check if new: return self.fetch('queueIssue&id=%s&new=True' % issueid, text=True) return self.fetch('queueIssue&id=%s' % issueid, text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def UnqueueIssue(self, issueid, name=''): self.logger.debug('unqued %s' % name) return self.fetch('unqueueIssue&id=%s' % issueid, text=True) @cherrypy.expose() @require() def DownloadIssue(self, issueid, name=''): """ downloads a issue via api and returns it to the browser """ self.logger.debug('Downloading issue %s' % name) getfile = self.fetch('downloadIssue&id=%s' % issueid, img=True) try: with closing(StringIO()) as f: f = StringIO() f.write(getfile) return cherrypy.lib.static.serve_fileobj( f.getvalue(), content_type='application/x-download', disposition=None, name=name, debug=False) except Exception as e: self.logger.error('Failed to download %s %s %s' % (name, issueid, e)) @cherrypy.expose() @cherrypy.tools.json_out() @require() def AddComic(self, id, **kwargs): self.logger.debug('Added %s to mylar' % kwargs.get('name', '')) return self.fetch('addComic&id=%s' % id) @cherrypy.expose() @cherrypy.tools.json_out() @require() def GetHistoryList(self): return self.fetch('getHistory') @cherrypy.expose() @require(member_of(htpc.role_user)) def ForceSearch(self): return self.fetch('forceSearch', text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def ForceProcess(self, dir_=None): if dir_: return self.fetch('forceProcess?dir_=%s' % dir_, text=True) return self.fetch('forceProcess', text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def ForceActiveArtistsUpdate(self): return self.fetch('forceActiveComicsUpdate', text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def ShutDown(self): return self.fetch('shutdown', text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def UpDate(self): return self.fetch('update', text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def ReStart(self): return self.fetch('restart', text=True) def fetch(self, command, url=None, api_key=None, img=False, json=True, text=False): url = Mylar._build_api_url(command, url, api_key) try: if img or text: json = False result = '' self.logger.debug('calling api @ %s' % url) # set a high timeout as some requests take a while.. response = requests.get(url, timeout=120, verify=False) if response.status_code != 200: self.logger.error('failed to contact mylar') return if text: result = response.text if img: result = response.content if json: result = response.json() #self.logger.debug('Response: %s' % result) return result except Exception as e: self.logger.error("Error calling api %s: %s" % (url, e)) @cherrypy.tools.json_out() @cherrypy.expose() @require(member_of(htpc.role_user)) def ping(self, mylar_enable, mylar_name, mylar_host, mylar_port, mylar_basepath, mylar_apikey, mylar_ssl=False, mylar_reverse_proxy_link=None): url = Mylar._build_url( mylar_ssl, mylar_host, mylar_port, mylar_basepath, ) return self.fetch('getVersion', url, mylar_apikey)
class Stats: def __init__(self): self.logger = logging.getLogger('modules.stats') htpc.MODULES.append({ 'name': 'Computer stats', 'id': 'stats', 'fields': [{ 'type': 'bool', 'label': 'Enable', 'name': 'stats_enable' }, { 'type': 'text', 'label': 'Menu name', 'name': 'stats_name' }, { 'type': 'bool', 'label': 'Bar', 'name': 'stats_use_bars' }, { 'type': 'text', 'label': 'Ignore filesystem', 'placeholder': 'NTFS FAT32', 'desc': 'Write the filesystems you want to ignore. Serperate with whitespace', 'name': 'stats_ignore_filesystem' }, { 'type': 'text', 'label': 'Ignore mountpoint', 'placeholder': 'mountpoint1 mountpoint2', 'desc': 'Write the mountpoints that you want to ignore.Seperate with whitepace', 'name': 'stats_ignore_mountpoint' }, { 'type': 'text', 'label': 'Limit processes', 'placeholder': '50', 'desc': 'Blank for all processes', 'name': 'stats_limit_processes' }] }) @cherrypy.expose() @require() def index(self): #Since many linux repos still have psutil version 0.5 if importPsutil and psutil.version_info >= (0, 7): pass else: self.logger.error("Psutil is outdated, needs atleast version 0,7") return htpc.LOOKUP.get_template('stats.html').render( scriptname='stats', importPsutil=importPsutil, cmdline=htpc.SHELL) @cherrypy.expose() @require() def uptime(self): try: if psutil.version_info >= (2, 0, 0): b = psutil.boot_time() else: b = psutil.get_boot_time() d = {} boot = datetime.now() - datetime.fromtimestamp(b) boot = str(boot) uptime = boot[:-7] d['uptime'] = uptime return json.dumps(d) except Exception as e: self.logger.error("Could not get uptime %s" % e) @cherrypy.expose() @require() def disk_usage(self): rr = None l = [] #Mount point that should be ignored, (Linux) Let me know if there is any missing ignore_mntpoint = [ '', '/dev/shm', '/lib/init/rw', '/sys/fs/cgroup', '/boot' ] #File systems that should be ignored ignore_fstypes = [ 'autofs', 'binfmt_misc', 'configfs', 'debugfs', 'devfs', 'devpts', 'devtmpfs', 'hugetlbfs', 'iso9660', 'linprocfs', 'mqueue', 'none', 'proc', 'procfs', 'pstore', 'rootfs', 'securityfs', 'sysfs', 'usbfs', '' ] #Adds the mointpoints that the user wants to ignore to the list of ignored ignorepoints user_ignore_mountpoint = htpc.settings.get('stats_ignore_mountpoint') #If user_ignore_mountpoint is a empty string if not user_ignore_mountpoint: pass else: ignore_mntpoint += user_ignore_mountpoint.split() #Adds the filesystem that the user wants to ignore to the list of ignored filesystem user_ignore_filesystem = htpc.settings.get('stats_ignore_filesystem') #If user_ignore_filsystem is a empty string if not user_ignore_filesystem: pass else: ignore_fstypes += user_ignore_filesystem.split() try: for disk in psutil.disk_partitions(all=True): # To stop windows barf on empy cdrom #File system that will be ignored #Mountpoint that should be ignored, linux if 'cdrom' in disk.opts or disk.fstype == '' or disk.fstype in ignore_fstypes or disk.mountpoint in ignore_mntpoint: continue usage = psutil.disk_usage(disk.mountpoint) dusage = usage._asdict() dusage['mountpoint'] = disk.mountpoint dusage['device'] = disk.device #NTFS driver reports filesystem type as fuseblk on Linux if disk.fstype == 'fuseblk': dusage['fstype'] = 'NTFS' else: dusage['fstype'] = disk.fstype l.append(dusage) rr = json.dumps(l) except Exception as e: self.logger.error("Could not get disk info %s" % e) return rr @cherrypy.expose() @require() def processes(self): rr = None limit = str(htpc.settings.get('stats_limit_processes')) procs = [] procs_status = {} for p in psutil.process_iter(): try: p.dict = p.as_dict([ 'username', 'get_memory_percent', 'create_time', 'get_cpu_percent', 'name', 'status', 'pid', 'get_memory_info' ]) #Create a readable time r_time = datetime.now() - datetime.fromtimestamp( p.dict['create_time']) r_time = str(r_time)[:-7] p.dict['r_time'] = r_time try: procs_status[p.dict['status']] += 1 except KeyError: procs_status[p.dict['status']] = 1 except psutil.NoSuchProcess: pass else: procs.append(p.dict) # return processes sorted by CPU percent usage processes = sorted(procs, key=lambda p: p['cpu_percent'], reverse=True) #Adds the total number of processes running, not in use atm processes.append(procs_status) #if limit is a empty string if not limit: rr = json.dumps(processes) else: rr = json.dumps(processes[:int(limit)]) return rr #Returns cpu usage @cherrypy.expose() @require() def cpu_percent(self): jcpu = None try: cpu = psutil.cpu_times_percent(interval=0.4, percpu=False) cpu = cpu._asdict() jcpu = json.dumps(cpu) return jcpu except Exception as e: self.logger.error("Error trying to pull cpu percent: %s" % e) # Not in use atm. @cherrypy.expose() @require() def cpu_times(self): rr = None try: cpu = psutil.cpu_times(percpu=False) dcpu = cpu._asdict() rr = json.dumps(dcpu) return rr except Exception as e: self.logger.error("Error trying to pull cpu times: %s" % e) #Not in use @cherrypy.expose() @require() def num_cpu(self): try: if psutil.version_info >= (2, 0, 0): cpu = psutil.cpu_count(logical=False) else: cpu = psutil.NUM_CPUS dcpu = cpu._asdict() jcpu = json.dumps(dcpu) return jcpu except Exception as e: self.logger.error("Error trying to pull cpu cores %s" % e) #Fetches info about the user that is logged in. @cherrypy.expose() @require() def get_user(self): l = [] d = {} rr = None try: for user in psutil.get_users(): duser = user._asdict() td = datetime.now() - datetime.fromtimestamp(duser['started']) td = str(td) td = td[:-7] duser['started'] = td rr = json.dumps(duser) return rr except Exception as e: self.logger.error("Pulling logged in info %s" % e) return rr @cherrypy.expose() @require() def get_local_ip(self): # added a small delay since getting local is faster then network usage (Does not render in the html) time.sleep(0.1) d = {} rr = None try: ip = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ip.connect(('8.8.8.8', 80)) local_ip = (ip.getsockname()[0]) d['localip'] = local_ip rr = json.dumps(d) return rr except Exception as e: self.logger.error("Pulling local ip %s" % e) @cherrypy.expose() @require() def get_external_ip(self): d = {} rr = None try: s = urllib2.urlopen('http://myexternalip.com/raw').read() d['externalip'] = s.strip() rr = json.dumps(d) return rr except Exception as e: self.logger.error("Pulling external ip %s" % e) @cherrypy.expose() @require() def sys_info(self): d = {} rr = None try: computer = platform.uname() d['system'] = computer[0] d['user'] = computer[1] d['release'] = computer[2] d['version'] = computer[3] d['machine'] = computer[4] d['processor'] = computer[5] rr = json.dumps(d) return rr except Exception as e: self.logger.error("Pulling system info %s" % e) #get network usage @cherrypy.expose() @require() def network_usage(self): try: nw_psutil = psutil.net_io_counters() dnw_psutil = nw_psutil._asdict() return json.dumps(dnw_psutil) except Exception as e: self.logger.error("Pulling network info %s" % e) @cherrypy.expose() @require() def virtual_memory(self): d = {} rr = None try: mem = psutil.virtual_memory() dmem = mem._asdict() rr = json.dumps(dmem) return rr except Exception as e: self.logger.error("Pulling physical memory %s" % e) @cherrypy.expose() @require() def swap_memory(self): d = {} rr = None try: mem = psutil.swap_memory() dmem = mem._asdict() rr = json.dumps(dmem) return rr except Exception as e: self.logger.error("Pulling swap memory %s" % e) #Fetches settings in the db, is used for some styling, like bars or tables. @cherrypy.expose() @require() def return_settings(self): d = {} try: if str(htpc.settings.get('stats_use_bars')) == str('False'): d['stats_use_bars'] = 'false' else: d['stats_use_bars'] = 'true' d['stats_ignore_mountpoint'] = htpc.settings.get( 'stats_ignore_mountpoint') d['stats_ignore_filesystem'] = htpc.settings.get( 'stats_ignore_filesystem') except Exception as e: self.logger.error("Getting stats settings %s" % e) return json.dumps(d) @cherrypy.expose() @require(member_of("admin")) def command(self, cmd=None, pid=None, signal=None): dmsg = {} jmsg = None try: if pid: p = psutil.Process(pid=int(pid)) name = p.name() else: pass if cmd == 'kill': try: p.terminate() dmsg['status'] = 'success' msg = 'Terminated process %s %s' % (name, pid) p.wait() except psutil.NoSuchProcess: msg = 'Process %s does not exist' % name except psutil.AccessDenied: msg = 'Dont have permission to terminate/kill %s %s' % ( name, pid) dmsg['status'] = 'error' except psutil.TimeoutExpired: p.kill() dmsg['status'] = 'success' msg = 'Killed process %s %s' % (name, pid) dmsg['msg'] = msg jmsg = json.dumps(dmsg) self.logger.info(msg) return jmsg elif cmd == 'signal': p.send_signal(signal) msg = '%ed pid %s successfully with %s' % (cmd, name, pid, signal) dmsg['msg'] = msg jmsg = json.dumps(dmsg) self.logger.info(msg) return jmsg except Exception as e: self.logger.error("Error trying to %s" % cmd, e) @cherrypy.expose() @require(member_of("admin")) def cmdpopen(self, cmd=None): d = {} cmd = cmd.split(', ') try: if htpc.SHELL: r = psutil.Popen(cmd, stdout=PIPE, stdin=PIPE, stderr=PIPE, shell=False) msg = r.communicate() d['msg'] = msg jmsg = json.dumps(d) self.logger.info(msg) return jmsg else: msg = 'HTPC-Manager is not started with --shell' self.logger.error(msg) d['msg'] = msg jmsg = json.dumps(d) self.logger.error(msg) return jmsg except Exception as e: self.logger.error('Sending command from stat module failed: %s' % e)
options['ignoreEpisodesWithoutFiles'] = True elif monitor == 'latest': season[i['seasonCount']]['monitored'] = True elif monitor == 'first': season[1]['monitored'] = True elif monitor == 'missing': options['ignoreEpisodesWithFiles'] = True elif monitor == 'existing': options['ignoreEpisodesWithoutFiles'] = True elif monitor == 'none': season_monitoring = False d['seasons'] = season d['addOptions'] = options self.logger.debug('%s' % dumps(d, indent=4)) # Manually add correct headers since @cherrypy.tools.json_out() renders it wrong cherrypy.response.headers['Content-Type'] = 'application/json' return self.fetch('Series', data=d, type='post') except Exception, e: self.logger.error('Failed to add tvshow %s %s' % (tvdbid, e)) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def test(self): tvshow = self.fetch('Series/lookup?term=tvdbid:70327') return tvshow
d['url'] = _url d['resize'] = resize_sizes imglist.append(d) self.logger.debug('Found %s images in total' % len(imglist)) try: if async: t = cachedprime(imglist, headers, resize=bool(resize)) return t except Exception as e: self.logger.debug('%s' % e) @cherrypy.expose() @require(member_of(htpc.role_admin)) @cherrypy.tools.json_out() def ping(self, kodi_server_host='', kodi_server_port='', kodi_server_username='', kodi_server_password='', **kwargs): """ Tests settings, returns MAC address on success and null on fail """ self.logger.debug("Testing kodi connectivity") try: url = kodi_server_host + ':' + kodi_server_port if kodi_server_username and kodi_server_password: url = kodi_server_username + ':' + kodi_server_password + '@' + url kodi = Server('http://' + url + '/jsonrpc') self.logger.debug("Trying to contact kodi via %s" % url) return kodi.XBMC.GetInfoLabels(labels=["Network.MacAddress"]) except Exception, e: self.logger.exception(e) self.logger.error("Unable to contact kodi via %s", url)
class NZBGet(object): def __init__(self): self.logger = logging.getLogger('modules.nzbget') htpc.MODULES.append({ 'name': 'NZBGet', 'id': 'nzbget', 'test': htpc.WEBDIR + 'nzbget/version', 'fields': [ { 'type': 'bool', 'label': 'Enable', 'name': 'nzbget_enable' }, { 'type': 'text', 'label': 'Menu name', 'name': 'nzbget_name' }, { 'type': 'text', 'label': 'IP / Host', 'placeholder': 'localhost', 'name': 'nzbget_host' }, { 'type': 'text', 'label': 'Port', 'placeholder': '6789', 'name': 'nzbget_port' }, { 'type': 'text', 'label': 'Basepath', 'placeholder': '/nzbget', 'name': 'nzbget_basepath' }, { 'type': 'text', 'label': 'User', 'name': 'nzbget_username' }, { 'type': 'password', 'label': 'Password', 'name': 'nzbget_password' }, { 'type': 'bool', 'label': 'Use SSL', 'name': 'nzbget_ssl' }, { 'type': 'text', 'label': 'Reverse proxy link', 'placeholder': '', 'desc': 'Reverse proxy link, e.g. https://nzbget.domain.com', 'name': 'nzbget_reverse_proxy_link' }, ] }) @cherrypy.expose() @require() def index(self): return htpc.LOOKUP.get_template('nzbget.html').render( scriptname='nzbget', webinterface=self.webinterface()) def nzbget_url(self): host = striphttp(htpc.settings.get('nzbget_host', '')) port = str(htpc.settings.get('nzbget_port', '')) username = htpc.settings.get('nzbget_username', '') password = htpc.settings.get('nzbget_password', '') nzbget_basepath = fix_basepath( htpc.settings.get('nzbget_basepath', '/')) ssl = 's' if htpc.settings.get('nzbget_ssl', True) else '' if username and password: authstring = '%s:%s@' % (username, password) else: authstring = '' url = 'http%s://%s%s:%s%sjsonrpc' % (ssl, authstring, host, port, nzbget_basepath) return url def webinterface(self): host = striphttp(htpc.settings.get('nzbget_host', '')) port = str(htpc.settings.get('nzbget_port', '')) username = htpc.settings.get('nzbget_username', '') password = htpc.settings.get('nzbget_password', '') nzbget_basepath = fix_basepath( htpc.settings.get('nzbget_basepath', '/')) ssl = 's' if htpc.settings.get('nzbget_ssl', True) else '' if username and password: authstring = '%s:%s@' % (username, password) else: authstring = '' url = 'http%s://%s%s:%s%s' % (ssl, authstring, host, port, nzbget_basepath) if htpc.settings.get('nzbget_reverse_proxy_link'): url = htpc.settings.get('nzbget_reverse_proxy_link') return url @cherrypy.expose() @require(member_of(htpc.role_admin)) @cherrypy.tools.json_out() def version(self, nzbget_host, nzbget_basepath, nzbget_port, nzbget_username, nzbget_password, nzbget_ssl=False, **kwargs): self.logger.debug("Fetching version information from nzbget") ssl = 's' if nzbget_ssl else '' nzbget_basepath = fix_basepath(nzbget_basepath) url = 'http%s://%s:%s%sjsonrpc/version' % ( ssl, striphttp(nzbget_host), nzbget_port, nzbget_basepath) try: if nzbget_username and nzbget_password: r = requests.get(url, timeout=10, auth=(nzbget_username, nzbget_password)) else: r = requests.get(url, timeout=10) return r.json() except: self.logger.error("Unable to contact nzbget via %s" % url) return @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetHistory(self): try: self.logger.debug("Fetching history") nzbget = jsonrpc.ServerProxy('%s' % self.nzbget_url()) return nzbget.history() except Exception as e: self.logger.error("Failed to get history %s" % e) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def Swap(self, nzbid=None, oldpos=None, newpos=None): self.logger.debug('Moving %s from %s to %s' % (nzbid, oldpos, newpos)) try: nzbget = jsonrpc.ServerProxy('%s' % self.nzbget_url()) relpos = int(newpos) - int(oldpos) return nzbget.editqueue("GroupMoveOffset", relpos, "", [int(nzbid)]) except Exception as e: self.logger.error("Failed to move %s from %s to %s %s" % (nzbid, oldpos, newpos, e)) @cherrypy.expose() @require() @cherrypy.tools.json_out() def AddNzbFromUrl(self, nzb_url='', nzb_category='', nzb_name='', f=''): self.logger.info("Added %s category %s url %s" % (nzb_name, nzb_category, nzb_url)) r = False try: nzbget = jsonrpc.ServerProxy('%s' % self.nzbget_url()) if f: # f = nzbfile r = nzbget.append(nzb_name, nzb_category, False, f) else: sess = requests.Session() nzb = sess.get(nzb_url, timeout=30) if not nzb_name: try: # Try to get the filename from the download # TODO check if x-dnzb-name is on all indexers nzb_name = nzb.headers.get('x-dnzb-name') except Exception as e: self.logger.error('%s' % e) self.logger.debug( 'Trying to parse the nzbname from content-disposition' ) nzb_name = nzb.headers.get( 'content-disposition').split( 'filename=')[1].replace('.nzb', '').replace('"', '') nzb = nzb.content r = nzbget.append(nzb_name, nzb_category, False, base64.standard_b64encode(nzb)) except Exception as e: self.logger.error("Failed to add %s %s to queue %s" % (nzb_name, nzb_url, e)) return r @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def ForceScan(self): self.logger.debug('Scan incoming directory') try: nzbget = jsonrpc.ServerProxy('%s' % self.nzbget_url()) return nzbget.scan() except Exception as e: self.logger.error('Failed while scanning incoming directory %s' % e) @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetCategorys(self): self.logger.debug('Fetching categories') categorys = [] # Add default category categorys.append('') try: nzbget = jsonrpc.ServerProxy('%s' % self.nzbget_url()) config = nzbget.config() r = re.compile(ur'(Category\d+.name)', re.IGNORECASE) for category in config: if re.match(r, category['Name']): categorys.append(category['Value']) except Exception as e: self.logger.error('Failed to fetch categorys %s' % e) return categorys @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def ChangeCategory(self, nzbid=None, cat=None, nzbname=None): self.logger.debug('Change %s nzbname id %s to category %s' % (nzbname, nzbid, cat)) r = False try: nzbget = jsonrpc.ServerProxy('%s' % self.nzbget_url()) if nzbid and cat: r = nzbget.editqueue("GroupSetCategory", 0, cat, [int(nzbid)]) except Exception as e: self.logger.error('Failed to set %s on %s' % (cat, nzbname, e)) return r @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetWarnings(self): try: self.logger.debug("Fetching warnings") nzbget = jsonrpc.ServerProxy('%s' % self.nzbget_url()) return nzbget.log(0, 1000) except Exception as e: self.logger.error("Failed to fetch warnings %s" % e) @cherrypy.expose() @require() @cherrypy.tools.json_out() def queue(self): try: self.logger.debug("Fetching queue") nzbget = jsonrpc.ServerProxy('%s' % self.nzbget_url()) return nzbget.listgroups() except Exception as e: self.logger.error("Failed to fetch queue %s" % e) @cherrypy.expose() @require() @cherrypy.tools.json_out() def status(self): try: self.logger.debug("Fetching status") nzbget = jsonrpc.ServerProxy('%s' % self.nzbget_url()) return nzbget.status() except Exception as e: self.logger.error("Failed to fetch queue %s" % e) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def QueueAction(self, action): try: self.logger.debug(action + " ALL") nzbget = jsonrpc.ServerProxy('%s' % self.nzbget_url()) if 'resume' in action: status = nzbget.resume() elif 'pause' in action: status = nzbget.pause() return status except Exception as e: self.logger.error("Failed to %s" % (action, e)) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def IndividualAction(self, nzbid='', name='', action=''): try: self.logger.debug("%s %s %s" % (action, name, nzbid)) nzbget = jsonrpc.ServerProxy('%s' % self.nzbget_url()) if 'resume' in action: action = 'GroupResume' elif 'pause' in action: action = 'GroupPause' elif 'delete' in action: action = 'GroupDelete' elif 'hidehistory' in action: action = 'HistoryDelete' status = nzbget.editqueue(action, 0, '', [int(nzbid)]) return status except Exception as e: self.logger.error("Failed to %s %s %s %s" % (action, name, nzbid, e)) return False @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def SetSpeed(self, speed): try: self.logger.debug("Setting speed-limit %s" % speed) nzbget = jsonrpc.ServerProxy('%s' % self.nzbget_url()) return nzbget.rate(int(speed)) except Exception as e: self.logger.error("Failed to set speed to %s %s" % (speed, e)) return False
class Sabnzbd(object): def __init__(self): self.logger = logging.getLogger('modules.sabnzbd') htpc.MODULES.append({ 'name': 'SABnzbd', 'id': 'sabnzbd', 'test': htpc.WEBDIR + 'sabnzbd/version', 'fields': [ {'type': 'bool', 'label': 'Enable', 'name': 'sabnzbd_enable'}, {'type': 'text', 'label': 'Menu name', 'name': 'sabnzbd_name'}, {'type': 'text', 'label': 'IP / Host', 'placeholder': 'localhost', 'name': 'sabnzbd_host'}, {'type': 'text', 'label': 'Port', 'placeholder': '8080', 'name': 'sabnzbd_port'}, {'type': 'text', 'label': 'Basepath', 'name': 'sabnzbd_basepath'}, {'type': 'text', 'label': 'API key', 'name': 'sabnzbd_apikey'}, {'type': 'bool', 'label': 'Use SSL', 'name': 'sabnzbd_ssl'}, {'type': 'text', 'label': 'Reverse proxy link', 'placeholder': '', 'desc': 'Reverse proxy link, e.g. https://sab.domain.com', 'name': 'sabnzbd_reverse_proxy_link'}, ] }) @cherrypy.expose() @require() def index(self): return htpc.LOOKUP.get_template('sabnzbd.html').render(scriptname='sabnzbd', webinterface=self.webinterface()) @cherrypy.expose() @require(member_of(htpc.role_admin)) @cherrypy.tools.json_out() def version(self, sabnzbd_host, sabnzbd_basepath, sabnzbd_port, sabnzbd_apikey, sabnzbd_ssl=False, **kwargs): self.logger.debug('Fetching version information from sabnzbd') ssl = 's' if sabnzbd_ssl else '' if not sabnzbd_basepath: sabnzbd_basepath = '/sabnzbd/' sabnzbd_basepath = fix_basepath(sabnzbd_basepath) url = 'http%s://%s:%s%sapi?output=json&apikey=%s' % (ssl, striphttp(sabnzbd_host), sabnzbd_port, sabnzbd_basepath, sabnzbd_apikey) try: return loads(urlopen(url + '&mode=version', timeout=10).read()) except: self.logger.error('Unable to contact sabnzbd via ' + url) return def webinterface(self): host = striphttp(htpc.settings.get('sabnzbd_host', '')) port = str(htpc.settings.get('sabnzbd_port', '')) basepath = htpc.settings.get('sabnzbd_basepath') ssl = 's' if htpc.settings.get('sabnzbd_ssl', 0) else '' if not basepath: basepath = '/sabnzbd/' sabnzbd_basepath = fix_basepath(basepath) url = 'http%s://%s:%s%s' % (ssl, host, port, sabnzbd_basepath) if htpc.settings.get('sabnzbd_reverse_proxy_link'): url = htpc.settings.get('sabnzbd_reverse_proxy_link') return url @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetHistory(self, limit=''): self.logger.debug('Fetching history') return self.fetch('&mode=history&limit=' + limit) @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetStatus(self): self.logger.debug('Fetching queue') return self.fetch('&mode=queue') @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetWarnings(self): self.logger.debug('Fetching warning') return self.fetch('&mode=warnings') @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def TogglePause(self, mode='', time=''): print mode, time if time: self.logger.debug('Pausing for %s minutes' % time) return self.fetch('&mode=config&name=set_pause&value=' + time) return self.fetch('&mode=' + mode) @cherrypy.expose() @require() @cherrypy.tools.json_out() def AddNzbFromUrl(self, nzb_url, nzb_category='', nzb_name=''): self.logger.debug('Adding nzb from url') self.logger.debug('%s %s %s' % (quote(nzb_url), nzb_category, nzb_name)) if 'api.nzbgeek.info' in nzb_url: nzb_url = nzb_url.replace('amp;', '') if nzb_category: nzb_category = '&cat=' + nzb_category return self.fetch('&mode=addurl&name=' + quote(nzb_url) + nzb_category + '&priority=2') @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def DeleteNzb(self, id): self.logger.debug('Deleting nzb') return self.fetch('&mode=queue&name=delete&value=' + id) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def DeleteHistory(self, id): self.logger.debug('Deleting history') return self.fetch('&mode=history&name=delete&value=' + id) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def Retry(self, id): self.logger.debug('Retry download') return self.fetch('&mode=retry&value=' + id) @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetCategories(self): self.logger.debug('Fetch available categories') return self.fetch('&mode=get_cats') @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def ChangeCategory(self, id, cat): self.logger.debug('Changing category of download') return self.fetch('&mode=change_cat&value=' + id + '&value2=' + cat) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def SetSpeed(self, speed): self.logger.debug('Setting speed-limit') return self.fetch('&mode=config&name=speedlimit&value=' + speed) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def Swap(self, v1, v2): self.logger.debug('Swaping nzb position to %s' % v2) return self.fetch('&mode=switch&value=%s&value2=%s' % (v1, v2)) def fetch(self, path): try: host = striphttp(htpc.settings.get('sabnzbd_host', '')) port = str(htpc.settings.get('sabnzbd_port', '')) apikey = htpc.settings.get('sabnzbd_apikey', '') sabnzbd_basepath = fix_basepath(htpc.settings.get('sabnzbd_basepath', '/sabnzbd/')) ssl = 's' if htpc.settings.get('sabnzbd_ssl', 0) else '' url = 'http%s://%s:%s%sapi?output=json&apikey=%s%s' % (ssl, host, port, sabnzbd_basepath, apikey, path) self.logger.debug('Fetching information from: %s' % url) return loads(urlopen(url, timeout=10).read(), strict=False) except Exception as e: self.logger.error('Cannot contact sabnzbd %s' % e) return
class Log: """ Root class """ def __init__(self): """ Initialize the logger """ self.logfile = os.path.join(htpc.DATADIR, 'htpcmanager.log') htpc.LOGGER = logging.getLogger() self.blacklistwords = BlackListFilter() # Disable colored stdout by --nocolor if htpc.NOCOLOR: self.logch = logging.StreamHandler() else: self.logch = ColorizingStreamHandler(sys.stdout) self.logfh = logging.handlers.RotatingFileHandler(self.logfile, maxBytes=25000000, backupCount=2) logformatter = logging.Formatter( '%(asctime)s :: %(name)s :: %(levelname)s :: %(message)s', "%Y-%m-%d %H:%M:%S") self.logch.setFormatter(logformatter) self.logfh.setFormatter(logformatter) if htpc.LOGLEVEL == 'debug' or htpc.DEV: loglevel = logging.DEBUG elif htpc.LOGLEVEL == 'info': loglevel = logging.INFO elif htpc.LOGLEVEL == 'warning': loglevel = logging.WARNING elif htpc.LOGLEVEL == 'error': loglevel = logging.ERROR else: loglevel = logging.CRITICAL self.logch.setLevel(loglevel) self.logfh.setLevel(loglevel) htpc.LOGGER.setLevel(loglevel) self.logch.addFilter(self.blacklistwords) self.logfh.addFilter(self.blacklistwords) # Disable cherrypy access log logging.getLogger('cherrypy.access').propagate = False # Disable urllib3 logger, except from criticals logging.getLogger("requests").setLevel(logging.CRITICAL) # Only show errors for paramiko logging.getLogger("paramiko").setLevel(logging.CRITICAL) # apscheduler # logging.getLogger("apscheduler.scheduler").setLevel(logging.CRITICAL) htpc.LOGGER.addHandler(self.logch) htpc.LOGGER.addHandler(self.logfh) htpc.LOGGER.info("Welcome to Hellowlol's HTPC Manager fork") htpc.LOGGER.info("Loglevel set to %s" % htpc.LOGLEVEL) @cherrypy.expose() @require() def index(self): """ Show log """ return htpc.LOOKUP.get_template('log.html').render(scriptname='log') @cherrypy.expose() @require() @cherrypy.tools.json_out() def getlog(self, lines=10, level=2): """ Get log as JSON """ levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'][-int(level):] content = [] try: for line in reversed(open(self.logfile, 'r').readlines()): line = line.split(' :: ') if len(line) > 1 and line[2] in levels: content.append(line) if len(content) >= int(lines): break except IOError: # Can't log this error since there is no log file. pass return content @cherrypy.expose() @require() @cherrypy.tools.json_out() def logit(self, **kw): ''' Used to log console errors ''' self.logger = logging.getLogger('webui.console.errors') if kw: self.logger.error("%s" % kw) return kw @cherrypy.expose() @cherrypy.tools.json_out() @require(member_of(htpc.role_admin)) def deletelog(self): try: open(self.logfile, 'w').close() return 'Log file deleted' except Exception, e: return 'Cannot delete log file: %s' % e
class Couchpotato(object): def __init__(self): self.logger = logging.getLogger('modules.couchpotato') htpc.MODULES.append({ 'name': 'CouchPotato', 'id': 'couchpotato', 'test': htpc.WEBDIR + 'couchpotato/getapikey', 'fields': [ {'type': 'bool', 'label': 'Enable', 'name': 'couchpotato_enable'}, {'type': 'text', 'label': 'Menu name', 'name': 'couchpotato_name'}, {'type': 'text', 'label': 'Username', 'name': 'couchpotato_username'}, {'type': 'password', 'label': 'Password', 'name': 'couchpotato_password'}, {'type': 'text', 'label': 'IP / Host *', 'name': 'couchpotato_host'}, {'type': 'text', 'label': 'Port', 'placeholder': '5050', 'name': 'couchpotato_port'}, {'type': 'text', 'label': 'Basepath', 'placeholder': '/couchpotato', 'name': 'couchpotato_basepath'}, {'type': 'text', 'label': 'API key', 'desc': 'Press test get apikey', 'name': 'couchpotato_apikey'}, {'type': 'bool', 'label': 'Use SSL', 'name': 'couchpotato_ssl'}, {'type': 'text', 'label': 'Reverse proxy link', 'placeholder': '', 'desc': 'Reverse proxy link ex: https://couchpotato.domain.com', 'name': 'couchpotato_reverse_proxy_link'}, ] }) @cherrypy.expose() @require() def index(self): return htpc.LOOKUP.get_template('couchpotato.html').render(scriptname='couchpotato', webinterface=self.webinterface()) def webinterface(self): ''' Generate page from template ''' ssl = 's' if htpc.settings.get('couchpotato_ssl', 0) else '' host = striphttp(htpc.settings.get('couchpotato_host', '')) port = str(htpc.settings.get('couchpotato_port', '')) basepath = fix_basepath(htpc.settings.get('couchpotato_basepath', '/')) url = 'http%s://%s:%s%s' % (ssl, host, port, basepath) if htpc.settings.get('couchpotato_reverse_proxy_link'): url = htpc.settings.get('couchpotato_reverse_proxy_link') return url def ctrl_c(self, filt): ctrl_char = '' if '!=' in filt: ctrl_char = '!=' elif '==' in filt: ctrl_char = '==' elif '<=' in filt: ctrl_char = '<=' elif '>=' in filt: ctrl_char = '>=' elif '<=' in filt: ctrl_char = '==' elif '!' in filt: ctrl_char = '!' elif '<' in filt: ctrl_char = '<' elif '>' in filt: ctrl_char = '>' elif '=' in filt: ctrl_char = '=' return ctrl_char def cp_filter(self, filt, collection): self.logger.debug('Called cp_filter %s' % filt) before = len(collection.get('movies', 0)) results = [] if collection.get('movies', ''): check = self.ctrl_c(filt) if filt: # default to fuzzy title search "16 blocks" if check == '': pat = '.*?'.join(map(re.escape, filt)) regex = re.compile(pat, flags=re.I) for m in collection['movies']: f = regex.search(m['title']) if f: results.append(m) else: # default to normal search if check: filt = filt.split(check) for m in collection['movies']: # flatten the info since we would normally be interessed in that if 'info' in m: for k, v in m['info'].iteritems(): m[k] = v try: imdb = m['info']['rating']['imdb'] m['rating'] = imdb[0] except: pass for k, v in m.iteritems(): if k.lower() == filt[0].lower(): if isinstance(v, dict): # actor roles='Jack Bauer' for kk, vv in v.iteritems(): if v == kk: results.append(m) elif isinstance(v, list): # genres=action if filt[1].lower() in [z.lower() for z in v]: results.append(m) elif isinstance(v, (int, float)): # for year!=1337 rating<=5.0 if check and check != '=': if comp_table[check](float(v), float(filt[1])): results.append(m) elif isinstance(v, basestring): # plot='some string' if filt[1].lower() in v.lower(): results.append(m) self.logger.debug('Filter out %s' % (before - len(results))) return results @cherrypy.expose() @require(member_of(htpc.role_admin)) @cherrypy.tools.json_out() def ping(self, couchpotato_host, couchpotato_port, couchpotato_apikey, couchpotato_basepath, couchpotato_ssl=False, **kwargs): self.logger.debug('Testing connectivity to couchpotato') couchpotato_basepath = fix_basepath(couchpotato_basepath) couchpotato_host = striphttp(couchpotato_host) ssl = 's' if couchpotato_ssl else '' url = 'http%s://%s:%s%sapi/%s' % (ssl, couchpotato_host, couchpotato_port, couchpotato_apikey) try: f = requests.get(url + '/app.available/', timeout=10) return f.json() except: self.logger.error('Unable to connect to couchpotato') self.logger.debug('connection-URL: %s' % url) return @cherrypy.expose() @require(member_of(htpc.role_admin)) @cherrypy.tools.json_out() def getapikey(self, couchpotato_username, couchpotato_password, couchpotato_host, couchpotato_port, couchpotato_apikey, couchpotato_basepath, couchpotato_ssl=False, **kwargs): self.logger.debug('Testing connectivity to couchpotato') if couchpotato_password and couchpotato_username != '': couchpotato_password = hashlib.md5(couchpotato_password).hexdigest() couchpotato_username = hashlib.md5(couchpotato_username).hexdigest() getkey = 'getkey/?p=%s&u=%s' % (couchpotato_password, couchpotato_username) couchpotato_basepath = fix_basepath(couchpotato_basepath) ssl = 's' if couchpotato_ssl else '' url = 'http%s://%s:%s%s%s' % (ssl, striphttp(couchpotato_host), couchpotato_port, couchpotato_basepath, getkey) try: f = requests.get(url, timeout=10, verify=False) return f.json() except Exception as e: self.logger.error('Unable to connect to couchpotato %s' % e) self.logger.debug('connection-URL: %s' % url) return @cherrypy.expose() @require() def GetImage(self, url, h=None, w=None, o=100, *args, **kwargs): # url can be a string or json working_url = None imgdir = os.path.join(htpc.DATADIR, 'images/') try: x = json.loads(url) if isinstance(x, list): tl = [(hashlib.md5(u).hexdigest(), u) for u in x] checkurl = [] # check any of the images exist in the cache for i in tl: if os.path.isfile(os.path.join(imgdir, i[0])): #self.logger.debug('%s exist in cache, ignore the rest of the hashes %s' % (str(i[1]), str(tl))) # dont bother checking any else if we have image checkurl = [] working_url = i[1] break else: checkurl.append(i) continue if working_url: return get_image(working_url, h, w, o) else: # None of the imges existed in the cache if checkurl: for ii, i in enumerate(checkurl): # verify that the download is ok before we try to cache it. try: r = requests.get(i[1], headers={'Cache-Control': 'private, max-age=0, no-cache, must-revalidate', 'Pragma': 'no-cache'}) if r.content: working_url = i[1] break except Exception as e: self.logger.error('Error: %s url: %s item: %s loop n : %s tuplelist %s' % (e, i[1], i, ii, str(tl))) if working_url: return get_image(working_url, h, w, o) except ValueError as e: if isinstance(url, basestring): return get_image(url, h, w, o) @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetMovieList(self, status='', limit='', f=''): self.logger.debug('Fetching Movies') if status == 'done': status += '&type=movie&release_status=done&status_or=1' data = self.fetch('media.list/?status=' + status) if f: filtered_movies = self.cp_filter(f, data) data['movies'] = filtered_movies data['total'] = len(filtered_movies) return data else: return data data = self.fetch('media.list/?status=' + status + '&limit_offset=' + limit) if f: filtered_movies = self.cp_filter(f, data) data['movies'] = filtered_movies data['total'] = len(filtered_movies) return data else: return data @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetNotificationList(self, limit='20'): self.logger.debug('Fetching Notification') data = self.fetch('notification.list/?limit_offset=' + limit) self.fetch('notification.markread') return data @cherrypy.expose() @require() @cherrypy.tools.json_out() def SearchMovie(self, q=''): self.logger.debug('Searching for movie') return self.fetch('movie.search/?q=' + q) @cherrypy.expose() @require() @cherrypy.tools.json_out() def AddMovie(self, movieid, profile, title, category_id=''): self.logger.debug('Adding movie') if category_id: return self.fetch('movie.add/?profile_id=' + profile + '&identifier=' + movieid + '&title=' + title + '&category_id=' + category_id) return self.fetch('movie.add/?profile_id=' + profile + '&identifier=' + movieid + '&title=' + title) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def EditMovie(self, id, profile, title): self.logger.debug('Editing movie') return self.fetch('movie.edit/?id=' + id + '&profile_id=' + profile + '&default_title=' + title) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def RefreshMovie(self, id): self.logger.debug('Refreshing movie') return self.fetch('movie.refresh/?id=' + id) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def DeleteMovie(self, id=''): self.logger.debug('Deleting movie') return self.fetch('movie.delete/?id=' + id) @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetReleases(self, id=''): self.logger.debug('Downloading movie') return self.fetch('media.get/?id=' + id) @cherrypy.expose() @require() @cherrypy.tools.json_out() def DownloadRelease(self, id=''): self.logger.debug('Downloading movie') return self.fetch('release.manual_download/?id=' + id) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def IgnoreRelease(self, id=''): self.logger.debug('Downloading movie') return self.fetch('release.ignore/?id=' + id) @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetProfiles(self): self.logger.debug('Fetching available profiles') return self.fetch('profile.list/') @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetCategories(self): self.logger.debug('Feching categories') return self.fetch('category.list') @cherrypy.expose() @require() @cherrypy.tools.json_out() def Suggestion(self): self.logger.debug('Fetching suggestion') return self.fetch('suggestion.view') @cherrypy.expose() @require() @cherrypy.tools.json_out() def ChartsView(self): self.logger.debug('Fetching charts') return self.fetch('charts.view') @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def SuggestionIgnore(self, imdb=None, seenit=None): u = 'suggestion.ignore/?imdb=%s' % imdb if seenit: u += '&seenit=1' self.logger.debug('Fetching suggestion') return self.fetch(u) @cherrypy.expose() @require() @cherrypy.tools.json_out() def DashboardSoon(self): return self.fetch('dashboard.soon') @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def Restart(self): return self.fetch('app.restart') @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def Shutdown(self): return self.fetch('app.shutdown') @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def Update(self): return self.fetch('updater.update') @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def SearchAllWanted(self): return self.fetch('movie.searcher.full_search') @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def Postprocess(self, path=''): u = 'renamer.scan' if path: u += '/?base_folder=%s' % path return self.fetch(u) def fetch(self, path): try: host = striphttp(htpc.settings.get('couchpotato_host', '')) port = str(htpc.settings.get('couchpotato_port', '')) apikey = htpc.settings.get('couchpotato_apikey', '') basepath = fix_basepath(htpc.settings.get('couchpotato_basepath', '/')) ssl = 's' if htpc.settings.get('couchpotato_ssl', 0) else '' url = 'http%s://%s:%s%sapi/%s/%s' % (ssl, host, port, basepath, apikey, path) self.logger.debug('Fetching information from: %s' % url) f = requests.get(url, timeout=60, verify=False) return f.json() except Exception as e: self.logger.debug('Exception: %s' % e) self.logger.error('Unable to fetch information') return
class Qbittorrent(object): session = requests.Session() def __init__(self): self.logger = logging.getLogger('modules.qbittorrent') self.newapi = False self.authenticated = False self.testapi = None htpc.MODULES.append({ 'name': 'qBittorrent', 'id': 'qbittorrent', 'test': htpc.WEBDIR + 'qbittorrent/ping', 'fields': [ { 'type': 'bool', 'label': 'Enable', 'name': 'qbittorrent_enable' }, { 'type': 'text', 'label': 'Menu name', 'name': 'qbittorrent_name' }, { 'type': 'text', 'label': 'IP / Host', 'placeholder': 'localhost', 'name': 'qbittorrent_host' }, { 'type': 'text', 'label': 'Port', 'placeholder': '8080', 'name': 'qbittorrent_port' }, { 'type': 'text', 'label': 'Username', 'name': 'qbittorrent_username' }, { 'type': 'password', 'label': 'Password', 'name': 'qbittorrent_password' }, { 'type': 'bool', 'label': 'Use SSL', 'name': 'qbittorrent_ssl' }, { 'type': 'text', 'label': 'Reverse proxy link', 'placeholder': '', 'desc': 'Reverse proxy link ex: https://qbt.domain.com', 'name': 'qbittorrent_reverse_proxy_link' }, ] }) @cherrypy.expose() @require() def index(self): return htpc.LOOKUP.get_template('qbittorrent.html').render( scriptname='qbittorrent', webinterface=self.webinterface()) def webinterface(self): host = striphttp(htpc.settings.get('qbittorrent_host', '')) port = htpc.settings.get('qbittorrent_port', '') ssl = 's' if htpc.settings.get('qbittorrent_ssl', 0) else '' url = 'http%s://%s:%s/' % (ssl, host, port) if htpc.settings.get('qbittorrent_reverse_proxy_link'): url = htpc.settings.get('qbittorrent_reverse_proxy_link') return url def qbturl(self): host = striphttp(htpc.settings.get('qbittorrent_host', '')) port = htpc.settings.get('qbittorrent_port', '') ssl = 's' if htpc.settings.get('qbittorrent_ssl', 0) else '' url = 'http%s://%s:%s/' % (ssl, host, port) return url @cherrypy.expose() @require() def login(self): self.logger.debug('Trying to login to qbittorrent') try: d = { 'username': htpc.settings.get('qbittorrent_username', ''), 'password': htpc.settings.get('qbittorrent_password', '') } # F33d da cookie monster r = self.session.post(self.qbturl() + 'login', data=d, verify=False, timeout=5) if r.content == 'Ok.': self.logger.debug('Successfully logged in with new api') self.authenticated = True self.newapi = True else: self.logger.error('Check your username and password') return r.content except Exception as e: self.logger.error('Failed to auth with new api %s' % e) return def _fetch(self, u, post=False, params={}, data=None): host = striphttp(htpc.settings.get('qbittorrent_host', '')) port = htpc.settings.get('qbittorrent_port', '') ssl = 's' if htpc.settings.get('qbittorrent_ssl') else '' url = 'http%s://%s:%s/' % (ssl, host, port) username = htpc.settings.get('qbittorrent_username', '') password = htpc.settings.get('qbittorrent_password', '') url += u if self.testapi is None: self.ping() if self.newapi: if self.authenticated is False: self.login() if post: if self.newapi: r = self.session.post(url, data=data, verify=False, timeout=8) else: r = self.session.post(url, data=data, verify=False, timeout=8, auth=HTTPDigestAuth(username, password)) else: if self.newapi: r = self.session.get(url, verify=False, timeout=8) else: r = self.session.get(url, verify=False, timeout=8, auth=HTTPDigestAuth(username, password)) return r @cherrypy.expose() @require() @cherrypy.tools.json_out() def fetch(self): try: if self.newapi: result = self._fetch( 'query/torrents?filter=all&sort=size&reverse=false') torrents = result.json() l = [] for torrent in torrents: t = {} for k, v in torrent.items(): t[k] = v if k == 'size': t['size'] = sizeof(int(v)) if k == 'eta': eta = time.strftime('%H:%M:%S', time.gmtime(v)) if eta == '00:00:00': eta = u'\u221E' t['eta'] = eta if k == 'ratio': t['ratio'] = math.ceil(v) l.append(t) return l else: result = self._fetch('json/torrents') # r.json() does not like the infinity return json.loads(result.content) except Exception as e: self.logger.error("Couldn't get torrents %s" % e) @cherrypy.expose() @require() @cherrypy.tools.json_out() def get_speed(self): ''' Get total download and upload speed ''' try: d = {} if not self.newapi: result = self._fetch('json/transferInfo/') result = result.json() speeddown = result['dl_info'] speedup = result['up_info'] list_of_down = speeddown.split() list_of_up = speedup.split() ds = list_of_down[1] + ' ' + list_of_down[2] dlstat = list_of_down[5] + ' ' + list_of_down[6] us = list_of_up[1] + ' ' + list_of_up[2] ulstat = list_of_down[5] + ' ' + list_of_down[6] d = { 'qbittorrent_speed_down': ds, 'qbittorrent_speed_up': us, 'qbittorrent_total_dl': dlstat, 'qbittorrent_total_ul': ulstat } else: # new api stuff result = self._fetch('query/transferInfo') result = result.json() d = { 'qbittorrent_speed_down': sizeof(result['dl_info_speed']), 'qbittorrent_speed_up': sizeof(result['up_info_speed']), 'qbittorrent_total_dl': sizeof(result['dl_info_data']), 'qbittorrent_total_ul': sizeof(result['up_info_data']) } return d except Exception as e: self.logger.error( "Couldn't get total download and uploads speed %s" % e) def get_global_dl_limit(self): try: result = self._fetch('command/getGlobalDlLimit/') speed = int(result.content) speed /= 1024 return speed except Exception as e: self.logger.error("Couldn't get global download limit %s" % e) def get_global_ul_limit(self): try: result = self._fetch('command/getGlobalUpLimit') speed = int(result.content) speed /= 1024 return speed except Exception as e: self.logger.error("Couldn't get global upload limit %s" % e) @cherrypy.expose() @require() @cherrypy.tools.json_out() def get_global_limit(self): try: d = {} d['dl_limit'] = self.get_global_dl_limit() d['ul_limit'] = self.get_global_ul_limit() return d except Exception as e: self.logger.debug( "Couldn't get global upload and download limits %s" % e) @cherrypy.expose() @require(member_of(htpc.role_user)) def command(self, cmd=None, hash=None, name=None, dlurl=None): ''' Handles pause, resume, delete singel torrents ''' try: self.logger.debug('%s %s' % (cmd, name)) data = {} if cmd == 'delete': data['hashes'] = hash elif cmd == 'download': data['urls'] = dlurl elif cmd == 'resumeall' or cmd == 'pauseall': # this does not work, bug in qbt see # https://github.com/qbittorrent/qBittorrent/issues/3016 if self.newapi: cmd = cmd[:-3] + 'All' else: data['hash'] = hash url = 'command/%s' % cmd # data is form encode.. r = self._fetch(url, post=True, data=data) return r.content except Exception as e: self.logger.error('Failed at %s %s %s %s' % (cmd, name, hash, e)) @cherrypy.expose() @require() def to_client(self, link, torrentname, **kwargs): ''' Is used by torrent search ''' try: url = 'command/download/' data = {} data['urls'] = link return self._fetch(url, data=data, post=True) self.logger.info('%s %s is sendt to qBittorrent' % (torrentname, link)) except Exception as e: self.logger.error('Failed to send %s %s to qBittorrent %s' % (link, torrentname, e)) @cherrypy.expose() @require(member_of(htpc.role_user)) def set_speedlimit(self, type=None, speed=None): ''' Sets global upload and download speed ''' try: self.logger.debug('Setting %s to %s' % (type, speed)) speed = int(speed) if speed == 0: speed = 0 else: speed = speed * 1024 url = 'command/' + type + '/' data = {} data['limit'] = speed r = self._fetch(url, data=data, post=True) return r.content except Exception as e: self.logger.error('Failed to set %s to %s %s' % (type, speed, e)) @cherrypy.expose() @require() # leave it as it uses this is get api version @cherrypy.tools.json_out() def ping(self, qbittorrent_host='', qbittorrent_port='', qbittorrent_username='', qbittorrent_password='', qbittorrent_ssl=False, **kw): self.logger.debug('Trying to connect to qBittorret') host = qbittorrent_host or htpc.settings.get('qbittorrent_host') port = qbittorrent_port or htpc.settings.get('qbittorrent_port') username = qbittorrent_username or htpc.settings.get( 'qbittorrent_username') password = qbittorrent_password or htpc.settings.get( 'qbittorrent_password') ssl = 's' if qbittorrent_ssl or htpc.settings.get( 'qbittorrent_ssl') else '' url = 'http%s://%s:%s/' % (ssl, host, port) self.newapi = False self.authenticated = False try: # We assume that its atleast 3.2 if this works. r = requests.get(url + 'version/api', timeout=8, verify=False) self.logger.debug('Trying to connect with new API %s' % r.url) # Old api returns a empty page if r.content != '' and r.ok: self.newapi = r.content self.testapi = True return r.content else: raise requests.ConnectionError except Exception as e: self.logger.debug( 'Failed to figure out what api version, trying old API') try: r = requests.post(url + 'json/torrents', auth=HTTPDigestAuth(username, password), timeout=10, verify=False) if r.ok: self.logger.debug('Old API works %s' % r.url) # Disable new api stuff self.testapi = True self.newapi = False self.authenticated = False except Exception as e: self.newapi = False self.authenticated = False self.logger.debug( 'Failed to contact qBittorrent via old and newapi') self.logger.error( 'Cant contact qBittorrent, check you settings and try again %s' % e)
class Headphones(object): def __init__(self): self.logger = logging.getLogger('modules.headphones') htpc.MODULES.append({ 'name': 'Headphones', 'id': 'headphones', 'test': htpc.WEBDIR + 'headphones/ping', 'fields': [{ 'type': 'bool', 'label': 'Enable', 'name': 'headphones_enable' }, { 'type': 'text', 'label': 'Menu name', 'name': 'headphones_name' }, { 'type': 'text', 'label': 'IP / Host *', 'name': 'headphones_host' }, { 'type': 'text', 'label': 'Port *', 'name': 'headphones_port' }, { 'type': 'text', 'label': 'Basepath', 'name': 'headphones_basepath' }, { 'type': 'text', 'label': 'API key', 'name': 'headphones_apikey' }, { 'type': 'bool', 'label': 'Use SSL', 'name': 'headphones_ssl' }, { 'type': 'text', "label": 'Reverse proxy link', 'placeholder': '', 'desc': 'Reverse proxy link ex: https://domain.com/hp', 'name': 'headphones_reverse_proxy_link' }] }) @cherrypy.expose() @require() def index(self): template = htpc.LOOKUP.get_template('headphones.html') settings = htpc.settings return template.render(scriptname='headphones', settings=settings, url=self.webinterface(), name=settings.get('headphones_name', 'Headphones')) def webinterface(self): url = Headphones._build_url() if htpc.settings.get('headphones_reverse_proxy_link'): url = htpc.settings.get('headphones_reverse_proxy_link') return url @cherrypy.expose() @require() def GetThumb(self, url=None, thumb=None, h=None, w=None, o=100): """ Parse thumb to get the url and send to htpc.proxy.get_image """ self.logger.debug("Trying to fetch image via %s" % url) if url is None and thumb is None: # To stop if the image is missing return # Should never used thumb, to lazy to remove it if thumb: url = thumb return get_image(url, h, w, o) @cherrypy.expose() @require() def viewArtist(self, artist_id): response = self.fetch('getArtist&id=%s' % artist_id) for a in response['albums']: a['StatusText'] = _get_status_icon(a['Status']) a['can_download'] = True if a['Status'] not in ( 'Downloaded', 'Snatched', 'Wanted') else False a['can_skip'] = True if a['Status'] not in ('Downloaded', 'Snatched', 'Skipped') else False a['can_trynew'] = True if a['Status'] in ('Snatched') else False template = htpc.LOOKUP.get_template('headphones_view_artist.html') return template.render( scriptname='headphones_view_artist', artist_id=artist_id, artist=response['artist'][0], artistimg=response['artist'][0]['ArtworkURL'], albums=response['albums'], description=response['description'][0], module_name=htpc.settings.get('headphones_name') or 'Headphones', ) @cherrypy.expose() @require() def viewAlbum(self, album_id): response = self.fetch('getAlbum&id=%s' % album_id) tracks = response['tracks'] for t in tracks: duration = t['TrackDuration'] total_seconds = duration / 1000 minutes = total_seconds / 60 seconds = total_seconds - (minutes * 60) t['DurationText'] = '%d:%02d' % (minutes, seconds) t['TrackStatus'] = _get_status_icon( 'Downloaded' if t['Location'] is not None else '') template = htpc.LOOKUP.get_template('headphones_view_album.html') return template.render(scriptname='headphones_view_album', artist_id=response['album'][0]['ArtistID'], album_id=album_id, albumimg=response['album'][0]['ArtworkURL'], module_name=htpc.settings.get( 'headphones_name', 'Headphones'), album=response['album'][0], tracks=response['tracks'], description=response['description'][0]) @staticmethod def _build_url(ssl=None, host=None, port=None, base_path=None): ssl = ssl or htpc.settings.get('headphones_ssl') host = host or htpc.settings.get('headphones_host') port = port or htpc.settings.get('headphones_port') base_path = base_path or htpc.settings.get('headphones_basepath') path = base_path or '/' if path.startswith('/') is False: path = '/' + path if path.endswith('/') is False: path += '/' url = '{protocol}://{host}:{port}{path}'.format( protocol='https' if ssl else 'http', host=striphttp(host), port=port, path=path, ) return url @staticmethod def _build_api_url(command, url=None, api_key=None): return '{url}api?apikey={api_key}&cmd={command}'.format( url=url or Headphones._build_url(), api_key=api_key or htpc.settings.get('headphones_apikey'), command=command, ) @cherrypy.expose() @cherrypy.tools.json_out() @require() def GetArtistList(self): return self.fetch('getIndex') @cherrypy.expose() @cherrypy.tools.json_out() @require() def GetWantedList(self): return self.fetch('getWanted') @cherrypy.expose() @cherrypy.tools.json_out() @require() def GetUpcomingList(self): return self.fetch('getUpcoming') @cherrypy.expose() @cherrypy.tools.json_out() @require() def SearchForArtist(self, name, searchtype): if searchtype == "artistId": return self.fetch('findArtist&%s' % urlencode( {'name': name.encode(encoding='UTF-8', errors='strict')})) else: return self.fetch('findAlbum&%s' % urlencode( {'name': name.encode(encoding='UTF-8', errors='strict')})) @cherrypy.expose() @require(member_of(htpc.role_user)) def RefreshArtist(self, artistId): return self.fetch('refreshArtist&id=%s' % artistId, text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def DeleteArtist(self, artistId): return self.fetch('delArtist&id=%s' % artistId, text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def PauseArtist(self, artistId): return self.fetch('pauseArtist&id=%s' % artistId, text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def ResumeArtist(self, artistId): return self.fetch('resumeArtist&id=%s' % artistId, text=True) @cherrypy.expose() @require() def QueueAlbum(self, albumId, new=False): # == Force check if new: return self.fetch('queueAlbum&id=%s&new=True' % albumId, text=True) return self.fetch('queueAlbum&id=%s' % albumId, text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def UnqueueAlbum(self, albumId): return self.fetch('unqueueAlbum&id=%s' % albumId, text=True) @cherrypy.expose() @cherrypy.tools.json_out() @require() def AddArtist(self, id, searchtype, **kwargs): if searchtype == "artistId": return self.fetch('addArtist&id=%s' % id) else: return self.fetch('addAlbum&id=%s' % id) @cherrypy.expose() @cherrypy.tools.json_out() @require() def GetHistoryList(self): return self.fetch('getHistory') @cherrypy.expose() @require() def GetAlbumArt(self, id): return self.fetch('getAlbumArt&id=%s' % id, img=True) @cherrypy.expose() @require() def GetAlbum(self, id): return self.fetch('getAlbum&id=%s' % id) @cherrypy.expose() @require(member_of(htpc.role_user)) def ForceSearch(self): return self.fetch('forceSearch', text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def ForceProcess(self, dir=None): if dir: return self.fetch('forceProcess?dir=%s' % dir, text=True) return self.fetch('forceProcess', text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def ForceActiveArtistsUpdate(self): return self.fetch('forceActiveArtistsUpdate', text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def ShutDown(self): return self.fetch('shutdown', text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def UpDate(self): return self.fetch('update', text=True) @cherrypy.expose() @require(member_of(htpc.role_user)) def ReStart(self): return self.fetch('restart', text=True) @cherrypy.expose() @cherrypy.tools.json_out() @require() def Choose_Specific_Download(self, id): return self.fetch('choose_specific_download&id=%s' % id) @cherrypy.expose() @cherrypy.tools.json_out() @require() def Download_Specific_Release(self, id, title, size, url, provider, kind): return self.fetch( 'download_specific_release&id=%s&title=%s&size=%s&url=%s&provider=%s&kind=%s' % (id, title, size, url, provider, kind)) def fetch(self, command, url=None, api_key=None, img=False, json=True, text=False): url = Headphones._build_api_url(command, url, api_key) try: # So shitty api.. if img or text: json = False result = '' self.logger.debug('calling api @ %s' % url) response = requests.get(url, timeout=30, verify=False) if response.status_code != 200: response.raise_for_status() self.logger.error('failed to contact headphones') return if text: result = response.text if img: result = response.content if json: result = response.json() self.logger.debug('Response: %s' % result) return result except Exception as e: self.logger.error("Error calling api %s: %s" % (url, e)) @cherrypy.expose() @cherrypy.tools.json_out() @require() def ping(self, headphones_enable, headphones_name, headphones_host, headphones_port, headphones_basepath, headphones_apikey, headphones_ssl=False, **kwargs): self.logger.debug('Attemping to ping headphones') url = Headphones._build_url( headphones_ssl, headphones_host, headphones_port, headphones_basepath, ) return self.fetch('getVersion', url, headphones_apikey)
class Settings: """ Main class """ def __init__(self): """ Create table on load if table doesnt exist """ self.logger = logging.getLogger('htpc.settings') self.logger.debug('Connecting to database: ' + htpc.DB) sqlhub.processConnection = connectionForURI('sqlite:' + htpc.DB) Setting.createTable(ifNotExists=True) @cherrypy.expose() @require(member_of("admin")) def index(self, **kwargs): """ Set keys if settings are received. Show settings page """ if kwargs: for key, val in kwargs.items(): self.set(key, val) return htpc.LOOKUP.get_template('settings.html').render( scriptname='settings', htpc=htpc) def get(self, key, defval=''): """ Get a setting from the database """ try: val = Setting.selectBy(key=key).getOne().val if val == 'on': return True elif val == "0": return False return val except SQLObjectNotFound: self.logger.debug("Unable to find the selected object: " + key) return defval def set(self, key, val): """ Save a setting to the database """ self.logger.debug("Saving settings to the database.") try: setting = Setting.selectBy(key=key).getOne() setting.val = val except SQLObjectNotFound: Setting(key=key, val=val) def get_templates(self): """ Get a list of available templates """ templates = [] for template in os.listdir(os.path.join(htpc.RUNDIR, "interfaces/")): current = bool(template == self.get('app_template', 'default')) templates.append({ 'name': template, 'value': template, 'selected': current }) return templates def get_themes(self): """ Get a list of available themes """ path = os.path.join(htpc.TEMPLATE, "css/themes/") themes = [] dirs = [ d for d in os.listdir(path) if os.path.isdir(os.path.join(path, d)) ] for theme in dirs: current = bool(theme == self.get('app_theme', 'default')) themes.append({'name': theme, 'value': theme, 'selected': current}) return themes """ Save json with custom urls """ @cherrypy.expose() @require(member_of("admin")) @cherrypy.tools.json_out() def urls(self, **kwargs): if kwargs: for key, val in kwargs.items(): self.set('custom_urls', key) """ Get custom defined urls from database in json format """ def getUrls(self): links = self.get('custom_urls', '{}') return loads(links)
class Sickbeard(object): def __init__(self): self.logger = logging.getLogger('modules.sickbeard') htpc.MODULES.append({ 'name': 'Sick Beard', 'id': 'sickbeard', 'test': htpc.WEBDIR + 'sickbeard/ping', 'fields': [ { 'type': 'bool', 'label': 'Enable', 'name': 'sickbeard_enable' }, { 'type': 'text', 'label': 'Menu name', 'name': 'sickbeard_name' }, { 'type': 'text', 'label': 'IP / Host', 'placeholder': 'localhost', 'name': 'sickbeard_host' }, { 'type': 'text', 'label': 'Port', 'placeholder': '8081', 'name': 'sickbeard_port' }, { 'type': 'text', 'label': 'Basepath', 'placeholder': '/sickbeard', 'name': 'sickbeard_basepath' }, { 'type': 'text', 'label': 'API key', 'name': 'sickbeard_apikey' }, { 'type': 'bool', 'label': 'Use SSL', 'name': 'sickbeard_ssl' }, { 'type': 'text', 'label': 'Reverse proxy link', 'placeholder': '', 'desc': 'Reverse proxy link ex: https://sb.domain.com', 'name': 'sickbeard_reverse_proxy_link' }, ] }) @cherrypy.expose() @require() def index(self): return htpc.LOOKUP.get_template('sickbeard.html').render( scriptname='sickbeard', webinterface=self.webinterface()) def webinterface(self): ''' Generate page from template ''' ssl = 's' if htpc.settings.get('sickbeard_ssl', 0) else '' host = striphttp(htpc.settings.get('sickbeard_host', '')) port = str(htpc.settings.get('sickbeard_port', '')) basepath = fix_basepath(htpc.settings.get('sickbeard_basepath', '/')) url = 'http%s://%s:%s%s' % (ssl, host, port, basepath) if htpc.settings.get('sickbeard_reverse_proxy_link'): url = htpc.settings.get('sickbeard_reverse_proxy_link') return url @cherrypy.expose() @require() def view(self, tvdbid): if not (tvdbid.isdigit()): raise cherrypy.HTTPError('500 Error', 'Invalid show ID.') self.logger.error('Invalid show ID was supplied: ' + str(tvdbid)) return False return htpc.LOOKUP.get_template('sickbeard_view.html').render( scriptname='sickbeard_view', tvdbid=tvdbid) @cherrypy.expose() @require(member_of(htpc.role_admin)) @cherrypy.tools.json_out() def ping(self, sickbeard_host, sickbeard_port, sickbeard_apikey, sickbeard_basepath, sickbeard_ssl=False, **kwargs): self.logger.info('Testing connectivity') ssl = 's' if sickbeard_ssl else '' if not sickbeard_basepath: sickbeard_basepath = fix_basepath(sickbeard_basepath) url = 'http%s://%s:%s%sapi/%s/?cmd=sb.ping' % ( ssl, striphttp(sickbeard_host), sickbeard_port, sickbeard_basepath, sickbeard_apikey) try: self.logger.debug('Trying to contact sickbeard via %s' % url) response = requests.get(url, timeout=10, verify=False) r = response.json() if r.get('result') == 'success': self.logger.debug('Sicbeard connectivity test success') return r except: self.logger.error('Unable to contact sickbeard via %s' % url) return @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetShowList(self): self.logger.debug('Fetching Show list') return self.fetch('shows&sort=name') @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetNextAired(self): self.logger.debug('Fetching Next Aired Episodes') return self.fetch('future') @cherrypy.expose() @require() def GetBanner(self, tvdbid): self.logger.debug('Fetching Banner') cherrypy.response.headers['Content-Type'] = 'image/jpeg' return self.fetch('show.getbanner&tvdbid=' + tvdbid, True) @cherrypy.expose() @require() def GetPoster(self, tvdbid): self.logger.debug('Fetching Poster') cherrypy.response.headers['Content-Type'] = 'image/jpeg' return self.fetch('show.getposter&tvdbid=' + tvdbid, True) @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetHistory(self, limit=''): self.logger.debug('Fetching History') return self.fetch('history&limit=' + limit) @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetLogs(self): self.logger.debug('Fetching Logs') return self.fetch('logs&min_level=info') @cherrypy.expose() @require() @cherrypy.tools.json_out() def AddShow(self, tvdbid): self.logger.debug('Adding a Show') return self.fetch('show.addnew&tvdbid=' + tvdbid) @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetShow(self, tvdbid): self.logger.debug('Fetching Show') return self.fetch('show&tvdbid=' + tvdbid) @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetEpisode(self, strShowID, strSeason, strEpisode): return self.fetch('episode&tvdbid=' + strShowID + '&season=' + strSeason + '&episode=' + strEpisode + '&full_path=1') @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetSeason(self, tvdbid, season): self.logger.debug('Fetching Season') return self.fetch('show.seasons&tvdbid=' + tvdbid + '&season=' + season) @cherrypy.expose() @require() @cherrypy.tools.json_out() def SearchEpisodeDownload(self, tvdbid, season, episode): self.logger.debug('Fetching Episode Downloads') return self.fetch( 'episode.search&tvdbid=' + tvdbid + '&season=' + season + '&episode=' + episode, False, 45) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def ForceFullUpdate(self, tvdbid): self.logger.debug('Force full update for tvdbid ' + tvdbid) return self.fetch('show.update&tvdbid=' + tvdbid) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def RescanFiles(self, tvdbid): self.logger.debug('Rescan all local files for tvdbid ' + tvdbid) return self.fetch('show.refresh&tvdbid=' + tvdbid) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def RemoveShow(self, tvdbid): self.logger.debug('Removing Show tvdbid ' + tvdbid) return self.fetch('show.delete&tvdbid=' + tvdbid) @cherrypy.expose() @require() def SearchShow(self, query): try: self.logger.debug('Searching thetvdb for %s' % query) url = 'http://www.thetvdb.com/api/GetSeries.php?seriesname=' + quote_plus( query) r = requests.get(url, timeout=10) return r.content except Exception as e: self.logger.error('Failed to fetch info from thetvdb about %s %s' % (query, e)) return @cherrypy.expose() @require(member_of(htpc.role_user)) def Postprocess(self, path=''): if path: path = '&%s' % path return self.fetch('postprocess' + path) @cherrypy.expose() @require(member_of(htpc.role_user)) def Shutdown(self): self.logger.debug('Shutting down Sickbeard') return self.fetch('sb.shutdown') @cherrypy.expose() @require(member_of(htpc.role_user)) def Restart(self): self.logger.debug('Restarting Sickbeard') return self.fetch('sb.restart') def fetch(self, cmd, img=False, timeout=10): try: host = htpc.settings.get('sickbeard_host', '') port = str(htpc.settings.get('sickbeard_port', '')) apikey = htpc.settings.get('sickbeard_apikey', '') ssl = 's' if htpc.settings.get('sickbeard_ssl', 0) else '' sickbeard_basepath = fix_basepath( htpc.settings.get('sickbeard_basepath', '/')) url = 'http' + ssl + '://' + host + ':' + str( port) + sickbeard_basepath + 'api/' + apikey + '/?cmd=' + cmd self.logger.debug('Fetching information from: %s' % url) if img is True: # Cache image return get_image(url) r = requests.get(url, timeout=timeout, verify=False) if r: r = r.json() return r except: self.logger.error('Unable to fetch information') return
class Settings(object): """ Main class """ def __init__(self): """ Create table on load if table doesnt exist """ self.logger = logging.getLogger('htpc.settings') self.logger.debug('Connecting to database: ' + htpc.DB) sqlhub.processConnection = connectionForURI('sqlite:' + htpc.DB) Setting.createTable(ifNotExists=True) self.updatebl() @cherrypy.expose() @require(member_of("admin")) def index(self, **kwargs): """ Set keys if settings are received. Show settings page """ if kwargs: for key, val in kwargs.items(): self.set(key, val) return htpc.LOOKUP.get_template('settings.html').render( scriptname='settings', htpc=htpc) def get(self, key, defval=''): """ Get a setting from the database """ try: val = Setting.selectBy(key=key).getOne().val if val in ['on', 1, '1']: return True elif val in ['off', "0", 0]: return False return val except SQLObjectNotFound: # Disabled this to not spam the log # self.logger.debug("Unable to find the selected object: " + key) return defval def set(self, key, val): """ Save a setting to the database """ self.logger.debug("Saving settings %s to the database." % key) try: setting = Setting.selectBy(key=key).getOne() setting.val = val # each time we save something to the db we want to blacklist it self.updatebl() except SQLObjectNotFound: Setting(key=key, val=val) self.updatebl() def updatebl(self): # fix me from modules.newznab import NewznabIndexers from modules.kodi import KodiServers from htpc.manageusers import Manageusers NewznabIndexers.createTable(ifNotExists=True) KodiServers.createTable(ifNotExists=True) Manageusers.createTable(ifNotExists=True) bl = [] fl = Setting.select().orderBy(Setting.q.key) for i in fl: if i.key.endswith("_apikey") or i.key.endswith( "_username") or i.key.endswith( "_password") or i.key.endswith("_passkey"): if len(i.val) > 1: bl.append(i.val) indexers = NewznabIndexers.select().orderBy(NewznabIndexers.q.apikey) for indexer in indexers: if len(indexer.apikey) > 1: bl.append(indexer.apikey) kodi = KodiServers.select().orderBy(KodiServers.q.password) for k in kodi: if len(k.password) > 1: bl.append(k.password) users = Manageusers.select().orderBy(Manageusers.q.username) for user in users: if len(user.password) > 1: bl.append(user.password) htpc.BLACKLISTWORDS = bl return bl def get_templates(self): """ Get a list of available templates """ templates = [] for template in os.listdir(os.path.join(htpc.RUNDIR, "interfaces/")): current = bool(template == self.get('app_template', 'default')) templates.append({ 'name': template, 'value': template, 'selected': current }) return templates def get_loglvl(self): """ Get a list of available templates """ loglvl = [] for lvl in ['info', 'debug', 'warning', 'error']: current = bool(lvl == self.get('app_loglevel', 'info')) loglvl.append({'name': lvl, 'value': lvl, 'selected': current}) return loglvl def get_themes(self): """ Get a list of available themes """ path = os.path.join(htpc.TEMPLATE, "css/themes/") themes = [] dirs = [ d for d in os.listdir(path) if os.path.isdir(os.path.join(path, d)) ] for theme in dirs: current = bool(theme == self.get('app_theme_mig', 'obsidian')) themes.append({'name': theme, 'value': theme, 'selected': current}) return themes """ Save json with custom urls """ @cherrypy.expose() @require(member_of("admin")) @cherrypy.tools.json_out() def urls(self, **kwargs): if kwargs: for key, val in kwargs.items(): self.set('custom_urls', key) """ Get custom defined urls from database in json format """ def getUrls(self): try: links = self.get('custom_urls', '{}') return loads(links) except: # Stop cherrypy from barfing is the user has entered invalid name/urls return loads('{}') @cherrypy.expose() @cherrypy.tools.json_out() @require(member_of("admin")) def delete_cache(self): try: cache_folder = os.path.join(htpc.DATADIR, 'images/') if os.path.exists(cache_folder): self.logger.info('Cache folder was deleted') shutil.rmtree(cache_folder) return {'success': 'true'} return {'failed': 'cache folder does not exist'} except Exception as e: self.logger.error('Failed to delete cache folder ', e) return {'failed': e} @cherrypy.expose() #@cherrypy.tools.json_out() @require(member_of("admin")) def test(self, *args, **kw): """ Used for testing stuff """ return 'test'
class Transmission(object): # Transmission Session ID sessionId = '' reqz = requests.Session() def __init__(self): self.logger = logging.getLogger('modules.transmission') htpc.MODULES.append({ 'name': 'Transmission', 'id': 'transmission', 'test': htpc.WEBDIR + 'transmission/ping', 'fields': [{ 'type': 'bool', 'label': 'Enable', 'name': 'transmission_enable' }, { 'type': 'text', 'label': 'Menu name', 'name': 'transmission_name' }, { 'type': 'text', 'label': 'IP / Host', 'placeholder': 'localhost', 'name': 'transmission_host' }, { 'type': 'text', 'label': 'Port', 'placeholder': '9091', 'name': 'transmission_port' }, { 'type': 'text', 'label': 'Reverse Proxy', 'placeholder': '', 'name': 'transmission_revproxy' }, { 'type': 'text', 'label': 'Rpc url', 'placeholder': '', 'name': 'transmission_rpcbasepath' }, { 'type': 'text', 'label': 'Username', 'name': 'transmission_username' }, { 'type': 'password', 'label': 'Password', 'name': 'transmission_password' }] }) @cherrypy.expose() @require() def index(self): return htpc.LOOKUP.get_template('transmission.html').render( scriptname='transmission') @cherrypy.expose() @require() @cherrypy.tools.json_out() def queue(self): fields = [ 'id', 'name', 'status', 'comment', 'downloadDir', 'downloadDir', 'percentDone', 'isFinished', 'eta', 'rateDownload', 'rateUpload', 'uploadRatio' ] return self.fetch('torrent-get', {'fields': fields}) @cherrypy.expose() @require() @cherrypy.tools.json_out() def stats(self): return self.fetch('session-stats') @cherrypy.expose() @require(member_of(htpc.role_admin)) @cherrypy.tools.json_out() def ping(self, **kwargs): ''' Test connection to Transmission ''' host = kwargs['transmission_host'] port = kwargs['transmission_port'] username = kwargs['transmission_username'] password = kwargs['transmission_password'] basepath = kwargs['transmission_rpcbasepath'] auth = None if not basepath: basepath = fix_basepath('/transmission/') url = 'http://%s:%s%srpc/' % (striphttp(host), port, basepath) # format post data data = {'method': 'session-get'} data = dumps(data) # Set Header header = { 'X-Transmission-Session-Id': self.sessionId, 'Content-Type': 'json; charset=UTF-8' } # Add authentication if username and password: auth = (username, password) try: r = self.reqz.post(url, data=data, timeout=10, headers=header, auth=auth) if r.ok: return r.json() else: if r.status_code == 409 and r.headers[ 'x-transmission-session-id']: self.logger.debug( 'Retry Transmission api with new session id.') res = self.renewsession(url, data, header, auth, r) return res except Exception as e: self.logger.error('Unable to fetch information from: %s %s' % (url, e)) return @cherrypy.expose() @require() @cherrypy.tools.json_out() def session(self): return self.fetch('session-get') @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def set_downspeed(self, speed): if int(speed) == 0: self.fetch('session-set', {'speed-limit-down': False}) return self.fetch('session-set', { 'speed-limit-down': int(speed), 'speed-limit-down-enabled': True }) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def set_upspeed(self, speed): if int(speed) == 0: self.fetch('session-set', {'speed-limit-up': 'false'}) else: return self.fetch('session-set', { 'speed-limit-up': int(speed), 'speed-limit-up-enabled': 'true' }) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def start(self, torrentId=False): if torrentId is False: return self.fetch('torrent-start-now') try: torrentId = int(torrentId) except ValueError: return False return self.fetch('torrent-start-now', {'ids': torrentId}) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def stop(self, torrentId=False): if torrentId is False: return self.fetch('torrent-stop') try: torrentId = int(torrentId) except ValueError: return False return self.fetch('torrent-stop', {'ids': torrentId}) @cherrypy.expose() @require() @cherrypy.tools.json_out() def Add(self, filename=None, metainfo=None): if metainfo: return self.fetch('torrent-add', {'metainfo': metainfo}) return self.fetch('torrent-add', {'filename': filename}) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def remove(self, torrentId): try: torrentId = int(torrentId) except ValueError: return False return self.fetch('torrent-remove', {'ids': torrentId}) #For torrent search @cherrypy.expose() @require() @cherrypy.tools.json_out() def to_client(self, link, torrentname, **kwargs): try: self.logger.info('Added %s to uTorrent' % torrentname) return self.fetch('torrent-add', {'filename': link}) except Exception as e: self.logger.debug('Failed to add %s to Transmission %s %s' ( torrentname, link, e)) # Wrapper to access the Transmission Api # If the first call fails, there probably is no valid Session ID so we try it again def fetch(self, method, arguments=''): ''' Do request to Transmission api ''' self.logger.debug('Request transmission method: ' + method) host = striphttp(htpc.settings.get('transmission_host', '')) port = str(htpc.settings.get('transmission_port', '')) basepath = htpc.settings.get('transmission_rpcbasepath') username = htpc.settings.get('transmission_username') password = htpc.settings.get('transmission_password') auth = None # Default basepath is transmission if not basepath: basepath = '/transmission/' basepath = fix_basepath(basepath) url = 'http://%s:%s%srpc/' % (host, str(port), basepath) # format post data data = {'method': method} if arguments: data['arguments'] = arguments data = dumps(data) # Set Header header = { 'X-Transmission-Session-Id': self.sessionId, 'Content-Type': 'json; charset=UTF-8' } if username and password: auth = (username, password) try: r = self.reqz.post(url, data=data, timeout=10, auth=auth, headers=header) if r.ok: return r.json() else: if r.status_code == 409 and r.headers[ 'x-transmission-session-id']: self.renewsession(url, data, header, auth, r) except Exception as e: self.logger.error('Unable to fetch information from: %s %s %s' % (url, data, e)) return def renewsession(self, url, data, header, auth, r): self.logger.debug('Retry Transmission api with new session id.') self.sessionId = r.headers['x-transmission-session-id'] header['X-Transmission-Session-Id'] = self.sessionId try: r = self.reqz.post(url, data=data, timeout=10, headers=header, auth=auth) if r.ok: return r.json() except Exception as e: self.logger.error( 'Unable access Transmission api with new session id. %s' % e)
class Newznab(object): def __init__(self): self.logger = logging.getLogger('modules.newznab') self.headers = {'User-Agent': 'HTPC-Manager'} NewznabIndexers.createTable(ifNotExists=True) htpc.MODULES.append({ 'name': 'Newznab', 'action': htpc.WEBDIR + 'newznab/setindexer', 'id': 'newznab', 'fields': [ {'type': 'bool', 'label': 'Enable', 'name': 'newznab_enable'}, {'type': 'text', 'label': 'Menu name', 'name': 'newznab_name'}, {'type': 'bool', 'label': 'Show link in menu', 'name': 'newznab_show_in_menu'}, {'type': 'select', 'label': 'Indexers', 'name': 'newznab_indexer_id', 'options': [ {'name': 'New', 'value': 0} ] }, {'type': 'text', 'label': 'Name', 'name': 'newznab_indexer_name'}, {'type': 'text', 'label': 'API URL', 'name': 'newznab_indexer_host'}, {'type': 'text', 'label': 'API KEY', 'name': 'newznab_indexer_apikey'}, {'type': 'bool', 'label': 'Use SSL', 'name': 'newznab_indexer_ssl'}, ] }) index = htpc.settings.get('newznab_current_indexer', 0) self.changeindexer(index) @cherrypy.expose() @require() def index(self, query='', **kwargs): return htpc.LOOKUP.get_template('newznab.html').render(query=query, scriptname='newznab') @cherrypy.expose() @require() @cherrypy.tools.json_out() def getindexer(self, id=None): if id: """ Get NewznabIndexers server info """ try: indexers = NewznabIndexers.selectBy(id=id).getOne() return dict((c, getattr(indexers, c)) for c in indexers.sqlmeta.columns) except SQLObjectNotFound: return """ Get a list of all servers and the current server """ all_indexers = [] for i in NewznabIndexers.select(): all_indexers.append({'id': i.id, 'name': i.name}) if len(all_indexers) < 1: return try: current = self.current.name except AttributeError: current = None return {'current': current, 'indexers': all_indexers} @cherrypy.expose() @require(member_of("admin")) def delindexer(self, id): """ Delete a server """ self.logger.debug("Deleting indexer %s" % id) NewznabIndexers.delete(id) self.changeindexer() return @cherrypy.expose() @require(member_of(htpc.role_admin)) @cherrypy.tools.json_out() def changeindexer(self, id=0): try: self.current = NewznabIndexers.selectBy(id=id).getOne() htpc.settings.set('newznab_current_indexer', str(id)) return "success" except SQLObjectNotFound: try: self.current = NewznabIndexers.select(limit=1).getOne() self.logger.error("Invalid indexer. Selecting first Available.") return "success" except SQLObjectNotFound: self.current = None self.logger.warning("No configured Indexers.") return "No valid indexers" @cherrypy.tools.json_out() @cherrypy.expose() @require(member_of(htpc.role_admin)) def setindexer(self, **kw): """ newznab_enable='', newznab_name='', newznab_show_in_menu='', newznab_indexer_id='', newznab_indexer_name='', newznab_indexer_host='', newznab_indexer_apikey='', newznab_indexer_ssl='', """ # kw is empty if kw is predef to '' for k, v in kw.items(): if k not in ('newznab_enable', 'newznab_name', 'newznab_show_in_menu', 'newznab_indexer_id', 'newznab_indexer_name', 'newznab_indexer_host', 'newznab_indexer_apikey', 'newznab_indexer_ssl'): del kw[k] # protection against the hack # only allow correct kw be save to db if k in ['newznab_enable', 'newznab_name', 'newznab_show_in_menu']: htpc.settings.set(k, v) # Clean hostname host = striphttp(kw.get('newznab_indexer_host')).rstrip('/') # make the search url ssl = 's' if kw.get('newznab_indexer_ssl') == 'on' else '' apiurl = 'http%s://%s/api?o=xml&apikey=%s&t=' % (ssl, host, kw.get('newznab_indexer_apikey')) if kw.get('newznab_indexer_id') == "0": self.logger.debug("Creating newznab indexer in database") try: indexer = NewznabIndexers(name=kw.get('newznab_indexer_name'), host=host, apikey=kw.get('newznab_indexer_apikey'), use_ssl=kw.get('newznab_indexer_ssl'), apiurl=apiurl) if kw.get('newznab_indexer_apikey') not in htpc.BLACKLISTWORDS: htpc.BLACKLISTWORDS.append(kw.get('newznab_indexer_apikey')) self.changeindexer(indexer.id) return 1 except Exception as e: self.logger.debug("Exception: %s" % e) self.logger.error("Unable to create newznab indexer in database") return 0 else: # Dont allow empty indexer be saved to db if host == '': self.logger.error('You must provide a url to the indexers %s' % kw.get('newznab_indexer_name', '')) return 0 self.logger.debug("Updating newznab indexer %s in database" % kw.get('newznab_indexer_name')) try: indexer = NewznabIndexers.selectBy(id=kw.get('newznab_indexer_id')).getOne() indexer.name = kw.get('newznab_indexer_name') indexer.host = host indexer.apikey = kw.get('newznab_indexer_apikey') indexer.use_ssl = kw.get('newznab_indexer_ssl', 'on') indexer.apiurl = apiurl return 1 except SQLObjectNotFound, e: self.logger.error("Unable to update %s in database %s" % (kw.get('newznab_indexer_name'), e)) return 0
except Exception, e: self.logger.debug('Failed to create %s %s' % (users_user_username, e)) return else: try: users = Manageusers.selectBy(username=users_user_username).getOne() users.username = users_user_username users.password = users_user_password users.role = users_user_role return 'hack' except SQLObjectNotFound, e: self.logger.debug('Failed to update username on %s' % users_user_username) return @cherrypy.expose() @require(member_of("admin")) @cherrypy.tools.json_out() def getuser(self, id=None): if id: """ Get user info, used by settings """ try: user = Manageusers.selectBy(id=id).getOne() return dict((c, getattr(user, c)) for c in user.sqlmeta.columns) except SQLObjectNotFound: return """ Get a list of all users""" users = [] for s in Manageusers.select(): users.append({'id': s.id, 'name': s.username}) if len(users) < 1:
class Sickrage(object): def __init__(self): self.logger = logging.getLogger('modules.sickrage') htpc.MODULES.append({ 'name': 'Sickrage', 'id': 'sickrage', 'test': htpc.WEBDIR + 'sickrage/ping', 'fields': [ {'type': 'bool', 'label': 'Enable', 'name': 'sickrage_enable'}, {'type': 'text', 'label': 'Menu name', 'name': 'sickrage_name'}, {'type': 'text', 'label': 'IP / Host', 'placeholder': 'localhost', 'name': 'sickrage_host'}, {'type': 'text', 'label': 'Port', 'placeholder': '8081', 'name': 'sickrage_port'}, {'type': 'text', 'label': 'Basepath', 'placeholder': '/sickrage', 'name': 'sickrage_basepath'}, {'type': 'text', 'label': 'API key', 'name': 'sickrage_apikey'}, {'type': 'bool', 'label': 'Use SSL', 'name': 'sickrage_ssl'}, {'type': 'text', 'label': 'Reverse proxy link', 'placeholder': '', 'desc':'Reverse proxy link, e.g. https://sr.domain.com', 'name': 'sickrage_reverse_proxy_link'} ] }) @cherrypy.expose() @require() def index(self): return htpc.LOOKUP.get_template('sickrage.html').render(scriptname='sickrage', webinterface=self.webinterface()) def webinterface(self): host = striphttp(htpc.settings.get('sickrage_host', '')) port = str(htpc.settings.get('sickrage_port', '')) apikey = htpc.settings.get('sickrage_apikey', '') ssl = 's' if htpc.settings.get('sickrage_ssl', 0) else '' sickrage_basepath = fix_basepath(htpc.settings.get('sickrage_basepath', '/')) url = 'http%s://%s:%s%s' % (ssl, host, port, sickrage_basepath) if htpc.settings.get('sickrage_reverse_proxy_link'): url = htpc.settings.get('sickrage_reverse_proxy_link') return url @cherrypy.expose() @require() def view(self, indexerid): if not (indexerid.isdigit()): raise cherrypy.HTTPError('500 Error', 'Invalid show ID.') self.logger.error('Invalid show ID was supplied: ' + str(indexerid)) return False return htpc.LOOKUP.get_template('sickrage_view.html').render(scriptname='sickrage_view', indexerid=indexerid) @cherrypy.expose() @require(member_of(htpc.role_admin)) @cherrypy.tools.json_out() def ping(self, sickrage_host, sickrage_port, sickrage_apikey, sickrage_basepath, sickrage_ssl=False, **kwargs): ssl = 's' if sickrage_ssl else '' self.logger.debug('Testing connectivity') try: sickrage_basepath = fix_basepath(sickrage_basepath) url = 'http%s://%s:%s%sapi/%s/?cmd=sb.ping' % (ssl, striphttp(sickrage_host), sickrage_port, sickrage_basepath, sickrage_apikey) self.logger.debug('Trying to contact sickrage via %s' % url) response = requests.get(url, timeout=10, verify=False) ret = response.json() if ret.get('result') == 'success': self.logger.debug('Sickrage connectivity test success') return ret except: self.logger.error('Unable to contact sickrage via %s' % url) return @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetShowList(self): self.logger.debug('Fetching Show list') return self.fetch('shows&sort=name', False, 200) @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetNextAired(self): self.logger.debug('Fetching Next Aired Episodes') return self.fetch('future') @cherrypy.expose() @require() def GetBanner(self, indexerid): self.logger.debug('Fetching Banner') cherrypy.response.headers['Content-Type'] = 'image/jpeg' return self.fetch('show.getbanner&indexerid=' + indexerid, True) @cherrypy.expose() @require() def GetPoster(self, indexerid): self.logger.debug('Fetching Poster') cherrypy.response.headers['Content-Type'] = 'image/jpeg' return self.fetch('show.getposter&indexerid=' + indexerid, True) @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetHistory(self, limit=''): self.logger.debug('Fetching History') return self.fetch('history&limit=' + limit) @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetLogs(self): self.logger.debug('Fetching Logs') return self.fetch('logs&min_level=info') @cherrypy.expose() @require() @cherrypy.tools.json_out() def AddShow(self, indexername='', indexerid='', **kwargs): # indexername=tvrageid or tvdbid self.logger.debug('Adding a Show') return self.fetch('show.addnew&' + urlencode(kwargs)) @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetShow(self, indexerid): self.logger.debug('Fetching Show') return self.fetch('show&indexerid=' + indexerid) @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetEpisode(self, strShowID, strSeason, strEpisode): return self.fetch('episode&indexerid=' + strShowID + '&season=' + strSeason + '&episode=' + strEpisode + '&full_path=1') @cherrypy.expose() @require() @cherrypy.tools.json_out() def GetSeason(self, indexerid, season): self.logger.debug('Fetching Season') return self.fetch('show.seasons&indexerid=' + indexerid + '&season=' + season) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def Postprocess(self, path='', force_replace=False, return_data=False, is_priority=False, type=False): self.logger.debug('Postprocess') if path: path = '&%s' % path return self.fetch('postprocess' + path, False, 120) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def Restart(self): self.logger.debug('Restart sr') return self.fetch('sb.restart', False, 15) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def SearchEpisodeDownload(self, indexerid, season, episode): self.logger.debug('Fetching Episode Downloads') return self.fetch('episode.search&indexerid=' + indexerid + '&season=' + season + '&episode=' + episode, False, 45) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def SearchSubtitle(self, indexerid, season, episode): self.logger.debug('Fetching subtitle') return self.fetch('episode.subtitlesearch&indexerid=' + indexerid + '&season=' + season + '&episode=' + episode, False, 45) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def Shutdown(self): self.logger.debug('Shutdown sickrage') return self.fetch('sb.shutdown', False, 20) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def ForceFullUpdate(self, indexerid): self.logger.debug('Force full update for indexerid %s' % indexerid) return self.fetch('show.update&indexerid=' + indexerid) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def RescanFiles(self, indexerid): self.logger.debug('Rescan all local files for indexerid %s' % indexerid) return self.fetch('show.refresh&indexerid=' + indexerid) @cherrypy.expose() @cherrypy.tools.json_out() @require(member_of(htpc.role_user)) def RemoveShow(self, indexerid, show_name=''): self.logger.debug('Delete %s from Sickrage indexerid %s' % (show_name, indexerid)) return self.fetch('show.delete&indexerid=%s' % indexerid) @cherrypy.expose() @cherrypy.tools.json_out() @require() def SearchShow(self, query): self.logger.debug('Searching tvdb and tvrage for %s query') return self.fetch('sb.searchindexers&indexer=0&name=%s' % quote(query), False, 60) @cherrypy.expose() @require() @cherrypy.tools.json_out() def ShowsStats(self): self.logger.debug('Grabbing tvrage statistics') return self.fetch('shows.stats') def fetch(self, cmd, img=False, timeout=20): try: host = striphttp(htpc.settings.get('sickrage_host', '')) port = str(htpc.settings.get('sickrage_port', '')) apikey = htpc.settings.get('sickrage_apikey', '') ssl = 's' if htpc.settings.get('sickrage_ssl', 0) else '' sickrage_basepath = fix_basepath(htpc.settings.get('sickrage_basepath', '/')) url = 'http%s://%s:%s%sapi/%s/?cmd=%s' % (ssl, host, port, sickrage_basepath, apikey, cmd) self.logger.debug('Fetching information from: %s' % url) if img is True: # Cache the images return get_image(url) res = requests.get(url, timeout=timeout, verify=False) return res.json() except Exception as e: self.logger.error('Unable to fetch information') self.logger.error(url) self.logger.error(e) return
class Kodi(object): def __init__(self): """ Add module to list of modules on load and set required settings """ self.logger = logging.getLogger('modules.kodi') KodiServers.createTable(ifNotExists=True) try: KodiServers.sqlmeta.addColumn(IntCol('starterport'), changeSchema=True) except: # Will always raise if column exist pass htpc.MODULES.append({ 'name': 'Kodi', 'id': 'kodi', 'fields': [ {'type': 'bool', 'label': 'Enable', 'name': 'kodi_enable'}, {'type': 'text', 'label': 'Menu name', 'name': 'kodi_name'}, {'type': 'bool', 'label': 'Enable PVR', 'name': 'kodi_enable_pvr'}, {'type': 'bool', 'label': 'Hide watched', 'name': 'kodi_hide_watched'} ] }) htpc.MODULES.append({ 'name': 'Kodi Servers', 'id': 'kodi_update_server', 'action': htpc.WEBDIR + 'kodi/setserver', 'test': htpc.WEBDIR + 'kodi/ping', 'fields': [ {'type': 'select', 'label': 'Server', 'name': 'kodi_server_id', 'options': [ {'name': 'New', 'value': 0} ] }, {'type': 'text', 'label': 'Name', 'name': 'kodi_server_name'}, {'type': 'text', 'label': 'IP / Host', 'placeholder': 'localhost', 'name': 'kodi_server_host'}, {'type': 'text', 'label': 'Port', 'placeholder': '8080', 'name': 'kodi_server_port'}, {'type': 'text', 'label': 'Username', 'name': 'kodi_server_username'}, {'type': 'password', 'label': 'Password', 'name': 'kodi_server_password'}, {'type': 'text', 'label': 'Mac addr.', 'name': 'kodi_server_mac'}, {'type': 'text', 'label': 'XBMC Starter port', 'placeholder': '9', 'name': 'kodi_server_starterport'} ] }) server = htpc.settings.get('kodi_current_server', 0) self.changeserver(server) @cherrypy.expose() @require() def index(self): """ Generate page from template """ return htpc.LOOKUP.get_template('kodi.html').render(scriptname='kodi') @cherrypy.expose() @require() def webinterface(self): """ Generate page from template """ raise cherrypy.HTTPRedirect(self.url('', True)) @cherrypy.expose() @cherrypy.tools.json_out() @require(member_of(htpc.role_admin)) def primecache(self, t='all', wanted_art='all', async=True, resize=True):
class Log: """ Root class """ def __init__(self): """ Initialize the logger """ self.logfile = os.path.join(htpc.DATADIR, 'htpcmanager.log') htpc.LOGGER = logging.getLogger() self.logch = logging.StreamHandler() self.logfh = logging.handlers.RotatingFileHandler(self.logfile, maxBytes=25000000, backupCount=2) logformatter = logging.Formatter( '%(asctime)s :: %(name)s :: %(levelname)s :: %(message)s', "%Y-%m-%d %H:%M:%S") self.logch.setFormatter(logformatter) self.logfh.setFormatter(logformatter) if htpc.LOGLEVEL == 'debug' or htpc.DEBUG: loglevel = logging.DEBUG elif htpc.LOGLEVEL == 'info': loglevel = logging.INFO elif htpc.LOGLEVEL == 'warning': loglevel = logging.WARNING elif htpc.LOGLEVEL == 'error': loglevel = logging.ERROR else: loglevel = logging.CRITICAL self.logch.setLevel(loglevel) self.logfh.setLevel(loglevel) htpc.LOGGER.setLevel(loglevel) # Disable cherrypy access log logging.getLogger('cherrypy.access').propagate = False htpc.LOGGER.addHandler(self.logch) htpc.LOGGER.addHandler(self.logfh) htpc.LOGGER.info("Welcome to HTPC-Manager!") htpc.LOGGER.info("Loglevel set to " + htpc.LOGLEVEL) @cherrypy.expose() @require() def index(self): """ Show log """ return htpc.LOOKUP.get_template('log.html').render(scriptname='log') @cherrypy.expose() @require() @cherrypy.tools.json_out() def getlog(self, lines=10, level=2): """ Get log as JSON """ levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'][-int(level):] content = [] try: for line in reversed(open(self.logfile, 'r').readlines()): line = line.split(' :: ') if len(line) > 1 and line[2] in levels: content.append(line) if len(content) >= int(lines): break except IOError: # Can't log this error since there is no log file. pass return content @cherrypy.expose() @cherrypy.tools.json_out() @require(member_of('admin')) def deletelog(self): try: open(self.logfile, 'w').close() return "Log file deleted" except Exception, e: return "Cannot delete log file: " + str(e)
class Sonarr(object): def __init__(self): self.logger = logging.getLogger('modules.sonarr') htpc.MODULES.append({ 'name': 'Sonarr', 'id': 'sonarr', 'test': htpc.WEBDIR + 'sonarr/Version', 'fields': [ {'type': 'bool', 'label': 'Enable', 'name': 'sonarr_enable'}, {'type': 'text', 'label': 'Menu name', 'name': 'sonarr_name'}, {'type': 'text', 'label': 'IP / Host', 'placeholder': 'localhost', 'name': 'sonarr_host'}, {'type': 'text', 'label': 'Port', 'placeholder': '8989', 'name': 'sonarr_port'}, {'type': 'text', 'label': 'Basepath', 'placeholder': '/sonarr', 'name': 'sonarr_basepath'}, {'type': 'text', 'label': 'API', 'name': 'sonarr_apikey'}, {'type': 'bool', 'label': 'Use SSL', 'name': 'sonarr_ssl'}, {'type': 'text', 'label': 'Reverse proxy link', 'placeholder': '', 'desc': 'Reverse proxy link, e.g. https://sonarr.domain.com', 'name': 'sonarr_reverse_proxy_link'}, ] }) @cherrypy.expose() @require() def index(self): return htpc.LOOKUP.get_template('sonarr.html').render(scriptname='sonarr', webinterface=self.webinterface(), quality=self.Profile()) def webinterface(self): host = striphttp(htpc.settings.get('sonarr_host', '')) port = str(htpc.settings.get('sonarr_port', '')) sonarr_basepath = htpc.settings.get('sonarr_basepath', '/') ssl = 's' if htpc.settings.get('sonarr_ssl', True) else '' # Makes sure that the basepath is /whatever/ sonarr_basepath = fix_basepath(sonarr_basepath) url = 'http%s://%s:%s%s' % (ssl, host, port, sonarr_basepath) if htpc.settings.get('sonarr_reverse_proxy_link'): url = htpc.settings.get('sonarr_reverse_proxy_link') return url def fetch(self, path, banner=None, type=None, data=None): try: host = striphttp(htpc.settings.get('sonarr_host', '')) port = str(htpc.settings.get('sonarr_port', '')) sonarr_basepath = htpc.settings.get('sonarr_basepath', '/') ssl = 's' if htpc.settings.get('sonarr_ssl', True) else '' # Makes sure that the basepath is /whatever/ sonarr_basepath = fix_basepath(sonarr_basepath) headers = {'X-Api-Key': htpc.settings.get('sonarr_apikey', '')} url = 'http%s://%s:%s%sapi/%s' % (ssl, host, port, sonarr_basepath, path) if banner: # the path includes the basepath automaticly (if fetched from api command 'Series') # Cache the image in HTPC Manager aswell. return get_image(url, headers=headers) if type == 'post': r = requests.post(url, data=dumps(data), headers=headers, verify=False) return r.content elif type == 'put': r = requests.put(url, data=dumps(data), headers=headers, verify=False) return r.content elif type == 'delete': r = requests.delete(url, data=dumps(data), headers=headers, verify=False) return r.content else: r = requests.get(url, headers=headers, verify=False) return loads(r.text) except Exception as e: self.logger.error('Failed to fetch url=%s path=%s error %s' % (url, path, e)) @cherrypy.expose() @require(member_of(htpc.role_admin)) def Version(self, sonarr_host, sonarr_port, sonarr_basepath, sonarr_apikey, sonarr_ssl=False, **kwargs): try: ssl = 's' if sonarr_ssl else '' if not sonarr_basepath: sonarr_basepath = fix_basepath(sonarr_basepath) headers = {'X-Api-Key': str(sonarr_apikey)} url = 'http%s://%s:%s%sapi/system/status' % (ssl, striphttp(sonarr_host), sonarr_port, sonarr_basepath) result = requests.get(url, headers=headers, verify=False) return result.json() except: return @cherrypy.expose() @require() @cherrypy.tools.json_out() def Rootfolder(self): return [folder['path'] for folder in self.fetch('Rootfolder')] @cherrypy.expose() @require() @cherrypy.tools.json_out() def Series(self): ''' Return info about all your shows ''' return self.fetch('Series') @cherrypy.expose() @require() @cherrypy.tools.json_out() def Show(self, id, tvdbid=None): ''' Details about one show ''' return self.fetch('Series/%s' % id) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def Delete_Show(self, id, title, delete_date=None): self.logger.debug('Deleted tvshow %s' % title) return self.fetch('Series/%s' % id, type='delete') @cherrypy.expose() @require() @cherrypy.tools.json_out() def History(self): return self.fetch('History?page=1&pageSize=100&sortKey=date&sortDir=desc') @cherrypy.expose() @require() @cherrypy.tools.json_out() def oldCalendar(self, param=None): return self.fetch('Calendar?end=%s' % (DT.date.today() + DT.timedelta(days=7))) @cherrypy.expose() @require() @cherrypy.tools.json_out() def Calendar(self, param=None, *args, **kwargs): p = urllib.urlencode(kwargs) episodes = self.fetch('Calendar?%s' % p) cal = [] for episode in episodes: d = { 'title': episode['series']['title'], 'season': episode['seasonNumber'], 'episode': episode['episodeNumber'], 'start': episode['airDateUtc'], 'overview': episode.get('overview', ''), 'all': episode, 'allDay': False, } cal.append(d) return cal @cherrypy.expose() @require() def View(self, tvdbid, id): if not (tvdbid.isdigit()): raise cherrypy.HTTPError('500 Error', 'Invalid show ID.') self.logger.error('Invalid show ID was supplied: ' + str(id)) return False # tvdbid is acctually id, and id is tvdbid.... return htpc.LOOKUP.get_template('sonarr_view.html').render(scriptname='sonarr_view', tvdbid=tvdbid, id=id) @cherrypy.expose() @require() @cherrypy.tools.json_out() def Episodes(self, id): return self.fetch('episode?seriesId=%s' % id) @cherrypy.expose() @require() def GetBanner(self, url=None): self.logger.debug('Fetching Banner') cherrypy.response.headers['Content-Type'] = 'image/jpeg' return self.fetch(url, banner=True) @cherrypy.expose() @require() @cherrypy.tools.json_out() def Episode(self, id): self.logger.debug('Fetching Episode info') return self.fetch('episode/%s' % id) @cherrypy.expose() @require() @cherrypy.tools.json_out() def Episodesqly(self, id): self.logger.debug('Fetching fileinfo for all episodes in a show') return self.fetch('episodefile?seriesId=%s' % id) @cherrypy.expose() @require() @cherrypy.tools.json_out() def Episodeqly(self, id): return self.fetch('episodefile/%s' % id) @cherrypy.expose() @require() @cherrypy.tools.json_out() def Profile(self): return self.fetch('profile') @cherrypy.expose() @require(member_of(htpc.role_user)) def Command(self, **kwargs): k = kwargs cherrypy.response.headers['Content-Type'] = 'application/json' try: data = {} data['name'] = k['method'] if k['par'] == 'episodeIds': k['id'] = [int(k['id'])] data[k['par']] = k['id'] except KeyError: pass return self.fetch(path='command', data=data, type='post') @cherrypy.expose() @require() @cherrypy.tools.json_out() def Lookup(self, q): return self.fetch('Series/lookup?term=%s' % urllib.quote(q)) @cherrypy.expose() @require() def AddShow(self, tvdbid, quality, monitor='all', seriestype='standard', rootfolder='', seasonfolder='on', specials=False): d = {} try: tvshow = self.fetch('Series/lookup?term=tvdbid:%s' % tvdbid) seasoncount = 1 season = [] for i in tvshow: self.logger.debug('monitor=%s' % monitor) d['title'] = i['title'] d['tvdbId'] = int(i['tvdbId']) d['qualityProfileId'] = int(quality) d['titleSlug'] = i['titleSlug'] d['RootFolderPath'] = rootfolder d['monitored'] = True season_monitoring = False d['seriesType'] = seriestype if seasonfolder == 'on': d['seasonFolder'] = True if specials == 'on': start_on_season = 0 else: start_on_season = 1 seasoncount += i['seasonCount'] options = {'ignoreEpisodesWithFiles': False, 'ignoreEpisodesWithoutFiles': False, 'searchForMissingEpisodes': True} if monitor == 'all': season_monitoring = True for x in xrange(start_on_season, int(seasoncount)): s = {'seasonNumber': x, 'monitored': season_monitoring} season.append(s) if monitor == 'future': options['ignoreEpisodesWithFiles'] = True options['ignoreEpisodesWithoutFiles'] = True elif monitor == 'latest': season[i['seasonCount']]['monitored'] = True elif monitor == 'first': season[1]['monitored'] = True elif monitor == 'missing': options['ignoreEpisodesWithFiles'] = True elif monitor == 'existing': options['ignoreEpisodesWithoutFiles'] = True elif monitor == 'none': season_monitoring = False d['seasons'] = season d['addOptions'] = options self.logger.debug('%s' % dumps(d, indent=4)) # Manually add correct headers since @cherrypy.tools.json_out() renders it wrong cherrypy.response.headers['Content-Type'] = 'application/json' return self.fetch('Series', data=d, type='post') except Exception, e: self.logger.error('Failed to add tvshow %s %s' % (tvdbid, e))
class Deluge(object): session = requests.Session() def __init__(self): self.logger = logging.getLogger('modules.deluge') htpc.MODULES.append({ 'name': 'Deluge', 'id': 'deluge', 'test': htpc.WEBDIR + 'deluge/ping', 'fields': [{ 'type': 'bool', 'label': 'Enable', 'name': 'deluge_enable' }, { 'type': 'text', 'label': 'Menu name', 'name': 'deluge_name' }, { 'type': 'text', 'label': 'IP / Host *', 'name': 'deluge_host' }, { 'type': 'text', 'label': 'Port *', 'name': 'deluge_port' }, { 'type': 'bool', 'label': 'Use SSL', 'name': 'deluge_ssl' }, { 'type': 'text', 'label': 'Basepath', 'name': 'deluge_basepath' }, { 'type': 'password', 'label': 'Password', 'name': 'deluge_password' }, { "type": "text", "label": "Reverse proxy link", "placeholder": "", "desc": "Reverse proxy link ex: https://deluge.domain.com", "name": "deluge_reverse_proxy_link" }] }) @cherrypy.expose() @require() def index(self): return htpc.LOOKUP.get_template('deluge.html').render( scriptname='deluge', webinterface=self.webinterface()) def webinterface(self): host = striphttp(htpc.settings.get('deluge_host', '')) port = str(htpc.settings.get('deluge_port', '')) deluge_basepath = fix_basepath(htpc.settings.get( 'deluge_basepath', '')) ssl = 's' if htpc.settings.get('deluge_ssl') else '' url = 'http%s://%s:%s%s' % (ssl, host, port, deluge_basepath) if htpc.settings.get('deluge_reverse_proxy_link'): url = htpc.settings.get('deluge_reverse_proxy_link') return url @cherrypy.expose() @require() @cherrypy.tools.json_out() def connected(self): return self.fetch('web.connected') @cherrypy.expose() @require() @cherrypy.tools.json_out() def connect(self, hostid): return self.fetch('web.connect', [hostid]) @cherrypy.expose() @require() @cherrypy.tools.json_out() def get_hosts(self): return self.fetch('web.get_hosts') @cherrypy.expose() @require() @cherrypy.tools.json_out() def queue(self): fields = [ 'progress', 'is_finished', 'ratio', 'name', 'download_payload_rate', 'upload_payload_rate', 'eta', 'state', 'hash', 'total_size' ] return self.fetch('core.get_torrents_status', [[], fields]) def q2(self): """ not in use atm, todo """ par = [ "queue", "name", "total_wanted", "state", "progress", "num_seeds", "total_seeds", "num_peers", "total_peers", "download_payload_rate", "upload_payload_rate", "eta", "ratio", "distributed_copies", "is_auto_managed", "time_added", "tracker_host", "save_path", "total_done", "total_uploaded", "max_download_speed", "max_upload_speed", "seeds_peers_ratio" ] return self.fetch('web.update_ui', [par, {}]) @cherrypy.expose() @require() @cherrypy.tools.json_out() def status(self): ''' quick ''' results = self.fetch( 'web.update_ui', [['payload_upload_rate', 'payload_download_rate, state'], {}]) if results['error'] is None: # py. 2.6.. d = dict(tuple(results['result']['filters']['state'])) results['result']['filters']['state'] = d return results @cherrypy.expose() @require() @cherrypy.tools.json_out() def stats(self): fields = ['payload_upload_rate', 'payload_download_rate, state'] return self.fetch('core.get_session_status', [fields]) @cherrypy.expose() @require() @cherrypy.tools.json_out() def start(self, torrentId): return self.fetch('core.resume_torrent', [[torrentId]]) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def stop(self, torrentId=None): return self.fetch('core.pause_torrent', [[torrentId]]) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def do_all(self, status): if status == 'resume': method = 'core.resume_all_torrents' else: method = 'core.pause_all_torrents' return self.fetch(method) @cherrypy.expose() @require() @cherrypy.tools.json_out() def daemon(self, status, port): if status == 'start': action = 'web.start_daemon' else: action = 'web.stop_daemon' return self.fetch(action, [int(port)]) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def set_dlspeed(self, speed): self.logger.debug('Set download speed to %s' % speed) if speed == '0': speed = -1 return self.fetch('core.set_config', [{ 'max_download_speed': int(speed) }]) @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def set_ulspeed(self, speed): if speed == '0': speed = -1 self.logger.debug('Set upload speed to %s' % speed) return self.fetch('core.set_config', [{ 'max_upload_speed': int(speed) }]) @cherrypy.expose() @require() @cherrypy.tools.json_out() def addtorrent(self, torrent, filename=''): result = self.fetch('core.add_torrent_file', [filename, torrent, {}]) return result ''' @cherrypy.expose() @require() @cherrypy.tools.json_out() def getconfig(self): #should be removed return self.fetch('core.get_config') ''' @cherrypy.expose() @require() @cherrypy.tools.json_out() def get_speed(self): ''' speed limit ''' result = self.fetch('core.get_config') # Dunno why the f, core.get_config_values didnt work... d = {} if result: d['max_download_speed'] = result['result']['max_download_speed'] d['max_upload_speed'] = result['result']['max_upload_speed'] result['result'] = d return result @cherrypy.expose() @require(member_of(htpc.role_user)) @cherrypy.tools.json_out() def remove(self, torrentId, removeData): removeDataBool = bool(int(removeData)) return self.fetch('core.remove_torrent', [torrentId, removeDataBool]) @cherrypy.expose() @require() @cherrypy.tools.json_out() def to_client(self, link='', torrentname='', **kwargs): try: self.logger.info('Added %s to deluge' % torrentname) # Find download path download_path = self.fetch('core.get_config_value', ['download_location']) if link.startswith('magnet'): path = link else: # deluge doesnt like a named download... link = link.split('?title=')[0] get_url = self.fetch('web.download_torrent_from_url', [link]) path = get_url['result'] return self.fetch('web.add_torrents', [[{ 'path': path, 'options': { 'download_location': download_path['result'] } }]]) except Exception as e: self.logger.debug('Failed adding %s to deluge %s %s' % (torrentname, link, e)) def fetch(self, method, arguments=None): """ Do request to Deluge api """ if arguments is None: arguments = [] host = striphttp(htpc.settings.get('deluge_host', '')) port = htpc.settings.get('deluge_port', '') deluge_basepath = fix_basepath( htpc.settings.get('deluge_basepath', '/')) ssl = 's' if htpc.settings.get('deluge_ssl') else '' url = 'http%s://%s:%s%sjson' % (ssl, host, port, deluge_basepath) self.logger.debug("Request deluge method: %s arguments %s" % (method, arguments)) try: # format post data data = {'id': 1, 'method': method, 'params': arguments} response = self.session.post(url, data=dumps(data), verify=False) result = response.json() if result and result['error']: self.logger.debug('Authenticating') self.session.post( url, data=dumps({ "method": "auth.login", "params": [htpc.settings.get('deluge_password', '')], "id": 1 }), verify=False) response = self.session.post(url, data=dumps(data), verify=False) return result except Exception as e: self.logger.error('Failed to fetch method %s arguments %s %s' % (method, arguments, e))