def message(self): ips = ', '.join(self.ips) warning = click.style('Warning:', bg='red', fg='white', bold=True) msg = """ {warning} not connected to a VPN The ip addresses in config.ini ({config_ips}) don't match your current ip address which is {current_ip}. If you know you are connected to a VPN, add your current ip to the config.ini file otherwise don't add it. Would you like me to open config.ini in your default editor so you can add {current_ip}? [y/n]""".format(config_ips=ips, current_ip=self.ip, warning=warning) msg = format_paragraphs(msg) click.echo(msg) c = click.getchar() click.echo() if c == 'y': click.edit(filename=Config.user_config) else: click.echo('Config is here: %s' % Config.user_config)
def message(self): ips = ', '.join(self.ips) warning = click.style('Warning:', bg='red', fg='white', bold=True) msg = """ {warning} not connected to a VPN Your current public ip ({ip}) is not in the good ip list. Would you like this ip added to the whitelist? [y/n]""".format( warning=warning, ip=self.ip) msg = format_paragraphs(msg) click.echo() click.echo(msg, nl=False) c = click.getchar() click.echo() if c == 'y': self.add_ip() return True return False
def ask(self): if not DB.get_config('telemetry_asked'): question = format_paragraphs(""" May TV Overlord report anonymous usage statistics? This is the single most useful thing a user can do. This data will help decide the features that get the most attention and the future direction of TV Overlord. More information at: https://github.com/8cylinder/tv-overlord/wiki/telemetry-data """) question = click.style(question, fg='green') click.echo() telemetry_ok = click.confirm(question, default=True) click.echo() DB.set_config('telemetry_ok', telemetry_ok) DB.set_config('telemetry_asked', True) if not DB.get_config('uuid4'): DB.set_config('uuid4', str(uuid.uuid4())) DB.set_config('uuid1', str(uuid.uuid1()))
def download(self, chosen_show, destination, search_type='torrent'): """ Pass the chosen show's data and destination to the providers download method and return the name of the file downloaded back to get-nzb.v2.py """ downloaded_filename = '' if chosen_show.startswith("magnet:"): # write magnet links to a file if Config.magnet_dir: Config.magnet_dir = os.path.expanduser(Config.magnet_dir) fn = self.magnet_filename() if os.path.isdir(Config.magnet_dir): full = os.path.join(Config.magnet_dir, fn) with open(full, 'w') as f: f.write(chosen_show) else: sys.exit('\n"%s" does not exist' % Config.magnet_dir) # use a command specified in config.ini elif Config.client: args = self.config_command(chosen_show) try: subprocess.Popen(args) except FileNotFoundError: sys.exit('\n"%s" not found.' % args[0]) elif platform.system() == 'Linux': err_msg = format_paragraphs(''' You do not have a default handler for magnet links. Either install a bittorent client or configure the "magnet folder" or "client" settings in config.ini.''') isX = True if os.environ.get('DISPLAY') else False if isX: app = 'xdg-open' else: click.echo() sys.exit(err_msg) try: subprocess.Popen([app, chosen_show], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) except OSError: click.echo() sys.exit(err_msg) elif platform.system() == 'Darwin': subprocess.Popen(["open", "--background", chosen_show]) elif platform.system() == 'Windows': os.startfile(chosen_show) else: unknown_system = platform.platform() sys.exit('\nUnknown system:', unknown_system) else: # is a nzb file final_name = '' # only cleans name for tv show downloads if self.season and self.episode: final_name = '%s.%s.nzb' % ( self.show_name.replace(' ', '.'), "S%sE%s" % (self.season.rjust(2, '0'), self.episode.rjust(2, '0'))) else: final_name = '%s.nzb' % (self.show_name) downloader = self.newsgroup_engines[0].Provider() downloaded_filename = downloader.download(chosen_show, destination, final_name) return downloaded_filename
def edit_db(search_str): try: import readline except ImportError: pass sql = 'SELECT * FROM shows WHERE name like :search' conn = sqlite3.connect(Config.db_file) conn.row_factory = tvu.dict_factory curs = conn.cursor() values = {'search': '%{}%'.format(search_str)} results = curs.execute(sql, values) data = [] for i in results: data.append(i) if len(data) == 0: sys.exit('"%s" not found' % search_str) elif len(data) > 1: click.echo('Multiple shows found, type a number to edit.') click.echo('Type "<ctrl> c" to cancel.') click.echo() for index, show in enumerate(data): click.echo(' %s. %s' % (index + 1, show['name'])) click.echo() choice = click.prompt( 'Choose number', default=1, type=click.IntRange(min=1, max=len(data))) idchoice = choice - 1 if idchoice not in range(len(data)): sys.exit('Invalid choice: %s' % choice) row = data[idchoice] else: row = data[0] editcolor = 'green' if Config.is_win else 31 dirty = False is_error = False click.echo() click.echo(tvu.format_paragraphs(''' While editing a field, hit <enter> in an empty field to leave it unchanged and skip to the next one. Type "<ctrl> c" to cancel all edits. The current value is shown in ()\'s beside the field name.''')) click.echo() title = '%s' % row['name'] click.echo(tvu.style(title, bold=True, ul=True)) click.echo() try: msg = tvu.style('Search engine name (%s): ', fg=editcolor) new_search_engine_name = input(msg % (row['search_engine_name'])) if not new_search_engine_name: new_search_engine_name = row['search_engine_name'] else: dirty = True msg = tvu.style('Current season (%s): ', fg=editcolor) new_season = input(msg % (row['season'])) if not new_season: new_season = str(row['season']) else: dirty = True msg = tvu.style('Last episode (%s): ', fg=editcolor) new_episode = input(msg % (row['episode'])) if not new_episode: new_episode = str(row['episode']) else: dirty = True msg = tvu.style('Status (%s): ', fg=editcolor) new_status = input(msg % (row['status'])) if not new_status: new_status = row['status'] else: dirty = True date_pretty = 'y' if row['search_by_date'] == 1 else 'n' msg = tvu.style('Search by date (%s) [y/n]: ', fg=editcolor) new_date = input(msg % (date_pretty)) if not new_date: new_date = date_pretty else: dirty = True new_format = row['date_format'] if new_date == 'y': click.echo(' The optional date format string can be any valid Python format string.') click.echo(' https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior') msg = tvu.style(' Date format string (%s): ', fg=editcolor) new_format = input(msg % (row['date_format'])) if not new_format: new_format = row['date_format'] else: dirty = True except KeyboardInterrupt: click.echo('\n\nDatabase edit cancelled.') sys.exit(0) click.echo() if dirty is False: click.echo('No changes made.') sys.exit(0) if not new_season.isdigit(): click.echo('Error: Season must be a number') is_error = True if not new_episode.isdigit(): click.echo('Error: Episode must be a number') is_error = True if new_status not in ['active', 'inactive']: click.echo('Error: Status must be either "active" or "inactive"') is_error = True if new_date not in ['y', 'n']: click.echo('Error: Search by date must be either "y" or "n"') is_error = True if is_error: sys.exit(1) if not click.confirm('Are these changes correct? (you can always change it back)', default='Y'): click.echo('\nDatabase edit cancelled.') sys.exit() sql = '''UPDATE shows SET season=:season, episode=:episode, status=:status, search_engine_name=:search_engine_name, search_by_date=:search_by_date, date_format=:date_format WHERE thetvdb_series_id=:tvdb_id''' row_values = { 'season': new_season, 'episode': new_episode, 'status': new_status, 'search_engine_name': new_search_engine_name, 'tvdb_id': row['thetvdb_series_id'], 'search_by_date': 1 if new_date == 'y' else 0, 'date_format': new_format, } curs.execute(sql, row_values) conn.commit() conn.close()
def _get_thetvdb_series_data(self): """Dynamicaly add all the data from Thetvdb.com networkid - None rating - 8.6 airs_dayofweek - Thursday contentrating - TV-14 seriesName - 30 Rock id - 79488 airs_time - 8:30 PM network - NBC fanart - http://www.thetvdb.com/banners/fanart/original/79488-11.jpg lastupdated - 1358045748 actors - |Tina Fey|Alec Baldwin|Tracy Morgan|Jane Krakowski|Kevin Brown|Maulik Pancholy|Keith Powell|Katrina Bowden|Lonny Ross|Scott Adsit|Judah Friedlander|Jack McBrayer|John Lutz|Grizz Chapman| ratingcount - 196 status - Continuing added - None poster - http://www.thetvdb.com/banners/posters/79488-7.jpg imdb_id - tt0496424 genre - |Comedy| banner - http://www.thetvdb.com/banners/graphical/79488-g11.jpg seriesid - 58326 language - en zap2it_id - SH00848357 addedby - None firstaired - 2006-10-11 runtime - 30 overview - Emmy Award Winner Tina Fey writ... """ # tv = tvdb_api.Tvdb(apikey=Config.thetvdb_apikey, # cache=Config.use_cache) # self.tvapi = tv # tv = self.tvapi tvdb_msg = format_paragraphs(''' An error occurred while retrieving data from thetvdb.com. This may mean that thetvdb is having issues. This error is usually caused by corrupted or incomplete data being returned from thetvdb. Keep retrying using the --no-cache flag or wait a while. Checking thetvdb's twitter feed sometimes gives current status: https://twitter.com/thetvdb Or isitdownrightnow.com: http://www.isitdownrightnow.com/thetvdb.com.html If the error continues, open an issue at GitHub and paste this error. https://github.com/8cylinder/tv-overlord/issues Error #: {error_no} Error message from stack trace: {stack_msg} ''') try: series = self.tvapi[self.db_name] self.show_exists = True except xml.etree.ElementTree.ParseError as e: print('-' * 20) print(e) print('-' * 20) except KeyError as e: # print('>>>', e) msg = tvdb_msg.format(error_no=101, stack_msg=e) click.echo(msg, err=True) sys.exit(1) except tvdb_api.tvdb_shownotfound: self.show_exists = False click.echo('\nShow not found: %s' % self.db_name, err=True) return except tvdb_api.tvdb_error as e: # msg = tvdb_msg.format(error_no=102, stack_msg=e) # click.echo(msg, err=True) # sys.exit(1) print('>>> ERROR', self.db_name) return except UnboundLocalError as e: msg = tvdb_msg.format(error_no=103, stack_msg=e) click.echo(msg, err=True) sys.exit(1) except requests.exceptions.ChunkedEncodingError as e: msg = tvdb_msg.format(error_no=104, stack_msg=e) click.echo(msg, err=True) sys.exit(1) except requests.exceptions.ConnectionError as e: msg = tvdb_msg.format(error_no=105, stack_msg=e) click.echo(msg, err=True) sys.exit(1) for i in series.data: setattr(self, i, series.data[i]) self.series = series
def add_new(self, name): try: result = self.tvapi.search(name) except KeyError as e: click.echo( 'Show data returned from TheTVDB.com has an error in it.', err=True) sys.exit(1) if not result: click.echo('No show found.') return else: click.echo('Multiple shows found, type a number to select.') click.echo('Type "<ctrl> c" to cancel.') click.echo() indent = ' ' for index, show in enumerate(result): title = show['seriesName'] click.echo(' %2s. ' % (index + 1), nl=False) click.secho(title, bold=True) if 'overview' in show: click.echo( format_paragraphs(show['overview'], indent=indent)) if 'firstaired' in show: click.secho('%sFirst aired: %s' % (indent, show['firstaired']), fg='green') click.echo() choice = click.prompt('Choose number (or [enter] for #1)', default=1, type=click.IntRange(min=1, max=len(result))) idchoice = choice - 1 show = result[idchoice] self.db_name = show['seriesName'] # name self._get_thetvdb_series_data() last_season = 1 last_episode = 0 for season in self.series: last_season = season for episode in self.series[season]: b_date = self.series[season][episode]['firstaired'] if not b_date: continue # some episodes have no broadcast date split_date = b_date.split('-') broadcast_date = datetime.datetime(int(split_date[0]), int(split_date[1]), int(split_date[2])) today = datetime.datetime.today() if broadcast_date > today: break last_episode = episode last_sxxexx = style(sxxexx(last_season, last_episode), bold=True) if int(last_season) == 1 and int(last_episode) == 0: se = 0 ep = 0 else: click.echo() click.echo('The last episode broadcast was %s.' % last_sxxexx) msg = 'Start downloading the [f]irst (or [enter]), [l]atest or season and episode?' start = click.prompt(msg, default='f') if start == 'f': se = ep = 0 elif start == 'l': se = last_season ep = last_episode else: try: se, ep = [int(i) for i in start.split()] except ValueError: sys.exit( 'Season and episode must be two numbers seperated by a space.' ) if ep > 0: ep -= 1 # episode in db is the NEXT episode msg = self._add_new_db(season=se, episode=ep) click.echo() click.echo(msg)
def _get_thetvdb_series_data(self): """Dynamicaly add all the data from Thetvdb.com networkid - None rating - 8.6 airs_dayofweek - Thursday contentrating - TV-14 seriesname - 30 Rock id - 79488 airs_time - 8:30 PM network - NBC fanart - http://www.thetvdb.com/banners/fanart/original/79488-11.jpg lastupdated - 1358045748 actors - |Tina Fey|Alec Baldwin|Tracy Morgan|Jane Krakowski|Kevin Brown|Maulik Pancholy|Keith Powell|Katrina Bowden|Lonny Ross|Scott Adsit|Judah Friedlander|Jack McBrayer|John Lutz|Grizz Chapman| ratingcount - 196 status - Continuing added - None poster - http://www.thetvdb.com/banners/posters/79488-7.jpg imdb_id - tt0496424 genre - |Comedy| banner - http://www.thetvdb.com/banners/graphical/79488-g11.jpg seriesid - 58326 language - en zap2it_id - SH00848357 addedby - None firstaired - 2006-10-11 runtime - 30 overview - Emmy Award Winner Tina Fey writ... """ # tv = tvdb_api.Tvdb(apikey=Config.thetvdb_apikey, # cache=Config.use_cache) # self.tvapi = tv # tv = self.tvapi tvdb_msg = format_paragraphs(''' An error occurred while retrieving data from thetvdb.com. This may mean that thetvdb is having issues. This error is usually caused by corrupted or incomplete data being returned from thetvdb. Keep retrying using the --no-cache flag or wait a while. Checking thetvdb's twitter feed sometimes gives current status: https://twitter.com/thetvdb Or isitdownrightnow.com: http://www.isitdownrightnow.com/thetvdb.com.html If the error continues, open an issue at GitHub and paste this error. https://github.com/8cylinder/tv-overlord/issues Error #: {error_no} Error message from stack trace: {stack_msg} ''') try: series = self.tvapi[self.db_name] self.show_exists = True except xml.etree.ElementTree.ParseError as e: print('-' * 20) print(e) print('-' * 20) except KeyError as e: # print('>>>', e) msg = tvdb_msg.format(error_no=101, stack_msg=e) click.echo(msg, err=True) sys.exit(1) except tvdb_api.tvdb_shownotfound: self.show_exists = False click.echo('\nShow not found: %s' % self.db_name, err=True) return except tvdb_api.tvdb_error as e: msg = tvdb_msg.format(error_no=102, stack_msg=e) click.echo(msg, err=True) sys.exit(1) except UnboundLocalError as e: msg = tvdb_msg.format(error_no=103, stack_msg=e) click.echo(msg, err=True) sys.exit(1) except requests.exceptions.ChunkedEncodingError as e: msg = tvdb_msg.format(error_no=104, stack_msg=e) click.echo(msg, err=True) sys.exit(1) except requests.exceptions.ConnectionError as e: msg = tvdb_msg.format(error_no=105, stack_msg=e) click.echo(msg, err=True) sys.exit(1) for i in series.data: setattr(self, i, series.data[i]) self.series = series
def add_new(self, name): try: result = self.tvapi.search(name) except KeyError as e: click.echo( 'Show data returned from TheTVDB.com has an error in it.', err=True) sys.exit(1) if not result: click.echo('No show found.') return else: click.echo('Multiple shows found, type a number to select.') click.echo('Type "<ctrl> c" to cancel.') click.echo() indent = ' ' for index, show in enumerate(result): title = show['seriesname'] click.echo(' %2s. ' % (index + 1), nl=False) click.secho(title, bold=True) if 'overview' in show: click.echo(format_paragraphs( show['overview'], indent=indent)) if 'firstaired' in show: click.secho( '%sFirst aired: %s' % (indent, show['firstaired']), fg='green') click.echo() choice = click.prompt( 'Choose number (or [enter] for #1)', default=1, type=click.IntRange(min=1, max=len(result))) idchoice = choice - 1 show = result[idchoice] self.db_name = show['seriesname'] # name self._get_thetvdb_series_data() last_season = 1 last_episode = 0 for season in self.series: last_season = season for episode in self.series[season]: b_date = self.series[season][episode]['firstaired'] if not b_date: continue # some episodes have no broadcast date split_date = b_date.split('-') broadcast_date = datetime.datetime( int(split_date[0]), int(split_date[1]), int(split_date[2])) today = datetime.datetime.today() if broadcast_date > today: break last_episode = episode last_sxxexx = style(sxxexx(last_season, last_episode), bold=True) if int(last_season) == 1 and int(last_episode) == 0: se = 0 ep = 0 else: click.echo() click.echo('The last episode broadcast was %s.' % last_sxxexx) msg = 'Start downloading the [f]irst (or [enter]), [l]atest or season and episode?' start = click.prompt(msg, default='f') if start == 'f': se = ep = 0 elif start == 'l': se = last_season ep = last_episode else: try: se, ep = [int(i) for i in start.split()] except ValueError: sys.exit('Season and episode must be two numbers seperated by a space.') if ep > 0: ep -= 1 # episode in db is the NEXT episode msg = self._add_new_db(season=se, episode=ep) click.echo() click.echo(msg)
def info(show_name, show_all, sort_by_next, db_status, ask_inactive, show_links, synopsis): """Show information about your tv shows. SHOW_NAME can be a full or partial name of a show. If used, it will show information about any shows that match that string, else it will show informaton about all your shows. """ show_info = {} counter = 0 status = 'active' # When the user specifies a single show, turn on --show-all # because the show they are asking for might an inactive show # and turn on --synopsis and --show-links since its only one # show we may as well show everything filter_name = '' if show_name: show_all = True synopsis = True show_links = True filter_name = show_name db_status = 'all' if Config.is_win: colors = {'links': 'blue', 'ended': 'red', 'last': 'cyan', 'future': 'green'} else: colors = {'links': 20, 'ended': 'red', 'last': 48, 'future': 22} shows = Shows(name_filter=filter_name, status=db_status) for show in shows: title = show.db_name if show.status == 'Ended': status = style(show.status, fg=colors['ended']) else: status = '' # build first row of info for each show se = 'Last downloaded: S%sE%s' % ( str(show.db_current_season).rjust(2, '0'), str(show.db_last_episode).rjust(2, '0'), ) se = style(se, fg=colors['last']) imdb_url = thetvdb_url = '' if show_links: imdb_url = style('\n IMDB.com: http://imdb.com/title/%s' % show.imdb_id, fg=colors['links']) thetvdb_url = style('\n TheTVDB.com: http://thetvdb.com/?tab=series&id=%s' % show.id, fg=colors['links']) synopsis_text = None if synopsis and show.overview: paragraph = show.overview indent = ' ' paragraph = format_paragraphs(paragraph, indent=indent) synopsis_text = '\n%s' % paragraph first_row_a = [] fancy_title = style(title, bold=True) for i in [fancy_title + ',', se, status, imdb_url, thetvdb_url, synopsis_text]: if i: first_row_a.append(i) first_row = ' '.join(first_row_a) # build 'upcoming episodes' list today = datetime.datetime.today() first_time = True episodes_list = [] counter += 1 for i in show.series: # season for j in show.series[i]: # episode b_date = show.series[i][j]['firstaired'] if not b_date: continue # some episode have no broadcast date? split_date = b_date.split('-') broadcast_date = datetime.datetime( int(split_date[0]), int(split_date[1]), int(split_date[2])) if not show_all: if broadcast_date < today: continue future_date = date_parser.parse(b_date) diff = future_date - today fancy_date = future_date.strftime('%b %d') if broadcast_date >= today: episodes_list.append('S%sE%s, %s (%s)' % ( show.series[i][j]['seasonnumber'].rjust(2, '0'), show.series[i][j]['episodenumber'].rjust(2, '0'), fancy_date, diff.days + 1, )) if first_time: first_time = False if sort_by_next: sort_key = str(diff.days).rjust(5, '0') + str(counter) else: sort_key = show.db_name.replace('The ', '') if not first_time: if episodes_list: indent = ' ' episode_list = 'Future episodes: ' + ' - '.join(episodes_list) episodes = format_paragraphs(episode_list, indent=indent) show_info[sort_key] = first_row + '\n' + episodes else: show_info[sort_key] = first_row if ask_inactive: if show.status == 'Ended' and first_time: click.echo( '%s has ended, and all have been downloaded. Set as inactive? [y/n]: ' % title) set_status = click.getchar() click.echo(set_status) if set_status == 'y': show.set_inactive() keys = list(show_info.keys()) keys.sort() full_output = '' for i in keys: full_output = full_output + show_info[i] + '\n\n' # remove the last '\n\n' full_output = full_output.strip() if len(keys) < 4: click.echo(full_output) elif Config.is_win: click.echo(full_output) else: click.echo_via_pager(full_output)
def download(self, chosen_show, destination, search_type='torrent'): """ Pass the chosen show's data and destination to the providers download method and return the name of the file downloaded back to get-nzb.v2.py """ downloaded_filename = '' if chosen_show.startswith("magnet:"): # write magnet links to a file if Config.magnet_dir: Config.magnet_dir = os.path.expanduser(Config.magnet_dir) fn = self.magnet_filename(chosen_show) if os.path.isdir(Config.magnet_dir): full = os.path.join(Config.magnet_dir, fn) with open(full, 'w') as f: f.write(chosen_show) else: sys.exit('\n"%s" does not exist' % Config.magnet_dir) # use a command specified in config.ini elif Config.client: args = self.config_command(chosen_show) try: subprocess.Popen(args) except FileNotFoundError: sys.exit('\n"%s" not found.' % args[0]) elif platform.system() == 'Linux': err_msg = tu.format_paragraphs(''' You do not have a default handler for magnet links. Either install a bittorent client or configure the "magnet folder" or "client" settings in config.ini.''') isX = True if os.environ.get('DISPLAY') else False if isX: app = 'xdg-open' else: click.echo() sys.exit(err_msg) try: subprocess.Popen([app, chosen_show], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) except OSError: click.echo() sys.exit(err_msg) elif platform.system() == 'Darwin': subprocess.Popen(["open", "--background", chosen_show]) elif platform.system() == 'Windows': os.startfile(chosen_show) else: unknown_system = platform.platform() sys.exit('\nUnknown system:', unknown_system) else: # is a nzb file final_name = '' show_fname = 'unknown' for f in self.episodes: if chosen_show == f[4]: show_fname = tu.clean_filename(f[0], strict=True) final_name = '%s.nzb' % (show_fname) idx, guid = chosen_show.split('|') downloader = self.newsgroup_engines[int(idx)] downloaded_filename = downloader.download( guid, destination, final_name) return downloaded_filename