def data_large_price(): from node_warden import load_config config = load_config(quiet=True) ft_config = config['MAIN'] font = ft_config.get('large_text_font') btc = btc_price_data() try: btc_price = cleanfloat(btc['DISPLAY']['BTC']['USD']['PRICE']) except Exception: return (error(' >> Error getting price data. Retrying...')) custom_fig = pyfiglet.Figlet(font=font) return_fig = custom_fig.renderText('$ ' + jformat(btc_price, 0)) return_fig = yellow(return_fig) chg_str = btc['DISPLAY']['BTC']['USD']['CHANGEPCTDAY'] chg = cleanfloat(chg_str) msg = '\n' if chg >= 0: msg += success(f'24hr Change: +{chg_str}%\n') if chg > 5: msg += (info("[NgU] ") + muted(f"Looks like Bitcoin is pumping ") + emoji.emojize(":rocket:")) if chg < 0: msg += error(f'24hr Change: {chg_str}%\n') if chg < -5: msg += muted( f"Bitcoin dropping? Buy the dip!\nTime to stack some sats. ") return_fig = muted(return_fig) return_fig += msg return (return_fig)
def data_random_satoshi(): from node_warden import load_config config = load_config(quiet=True) url = config['QUOTES'].get('url') try: quotes = tor_request(url).json() except Exception: return (error(' >> Error contacting server. Retrying... ')) quote = quotes[randrange(len(quotes))] return_str = info(f"Satoshi Quotes | Subject: {quote['category']}\n") return_str += muted(f"{quote['date']} on {quote['medium']}\n") return_str += yellow(f"{quote['text']} \n\n") return_str += muted("Source: Nakamoto Institute") return (return_str)
def check_for_pump(_loop, _data): try: btc = btc_price_data() btc_price = btc['DISPLAY']['BTC']['USD']['PRICE'] chg_str = btc['DISPLAY']['BTC']['USD']['CHANGEPCTDAY'] chg = cleanfloat(chg_str) if chg > 5: logging.info( info("[NgU] ") + muted(f"Looks like Bitcoin is pumping ") + emoji.emojize(":rocket:") + yellow(f' {btc_price}') + success(f' +{chg_str}%')) if chg < -5: logging.info( info("[NgU] ") + muted( f"Looks like Bitcoin is dropping. Time to stack some sats. " ) + yellow(f' {btc_price}') + error(f' {chg_str}%')) except Exception: pass main_loop.set_alarm_in(300, check_for_pump)
def data_login(): tabs = [] processes = subprocess.check_output("last") processes = list(processes.splitlines()) for process in processes: try: process = process.decode("utf-8") if 'still' not in process or 'boot' in process: continue user = process.split()[0] process = process.replace(user, '') console = process.split()[0] process = process.replace(console, '') date_str = parser.parse(process, fuzzy=True) # Check if someone logged in the last 60 minutes expiration = 60 too_soon = datetime.now() - timedelta(minutes=expiration) if date_str > too_soon: warn = warning(emoji.emojize(':warning:')) tabs.append([ f" {warn} {error(f'Recent Login (last {expiration} min)')}:" ]) tabs.append([ f" {warning(user)} at {muted(console)} " + bold( f"logged on {success(date_str.strftime('%H:%M (%b-%d)' ))}" ) ]) except Exception: tabs.append([f" {process}"]) if tabs != []: tabs = tabulate(tabs, headers=['Users Logged in to this computer'], colalign=["left"]) else: tabs = muted( 'List of users logged in to this computer is empty.\nEither no outside users are logged in or login info is not available.' ) return (tabs)
spinner.spinner = Spinners.moon try: engine = pyttsx3.init() engine.setProperty('rate', 270) engine.say(config['MAIN'].get('welcome_text')) engine.runAndWait() except Exception: pass spinner.stop() spinner.write("") if __name__ == '__main__': clear_screen() logo() print("") try: os.remove(debug_file) except Exception: pass launch_logger() logging.info(muted("Starting main program...")) config = load_config() tor = create_tor() check_version() greetings() with yaspin(text="Launching Dashboard. Please Wait...", color="cyan") as spinner: main_dashboard(config, tor, spinner)
def check_cryptocompare(): from utils import pickle_it with yaspin(text="Testing price grab from Cryptocompare", color="green") as spinner: data = {'Response': 'Error', 'Message': None} try: api_key = pickle_it('load', 'cryptocompare_api.pkl') if api_key != 'file not found': baseURL = ( "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=BTC" + "&tsyms=USD&api_key=" + api_key) req = requests.get(baseURL) data = req.json() btc_price = (data['DISPLAY']['BTC']['USD']['PRICE']) spinner.text = (success(f"BTC price is: {btc_price}")) spinner.ok("✅ ") pickle_it('save', 'cryptocompare_api.pkl', api_key) return else: data = {'Response': 'Error', 'Message': 'No API Key is set'} except Exception as e: data = {'Response': 'Error', 'Message': str(e)} logging.error(data) try: if data['Response'] == 'Error': spinner.color = 'yellow' spinner.text = "CryptoCompare Returned an error " + data[ 'Message'] # ++++++++++++++++++++++++++ # Load Legacy # ++++++++++++++++++++++++++ try: # Let's try to use one of the # legacy api keys stored under cryptocompare_api.keys file # You can add as many as you'd like there filename = 'warden/static/cryptocompare_api.keys' file = open(filename, 'r') for line in file: legacy_key = str(line) spinner.text = ( warning(f"Trying different API Keys...")) baseURL = ( "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=BTC" + "&tsyms=USD&api_key=" + legacy_key) try: data = None logging.debug(f"Trying API Key {legacy_key}") request = requests.get(baseURL) data = request.json() btc_price = ( data['DISPLAY']['BTC']['USD']['PRICE']) spinner.text = ( success(f"BTC price is: {btc_price}")) spinner.ok("✅ ") logging.debug(f"API Key {legacy_key} Success") pickle_it('save', 'cryptocompare_api.pkl', legacy_key) return except Exception as e: logging.debug(f"API Key {legacy_key} ERROR: {e}") logging.debug( f"API Key {legacy_key} Returned: {data}") spinner.text = "Didn't work... Trying another." except Exception: pass spinner.text = (error("Failed to get API Key - read below.")) spinner.fail("[!]") print( ' -----------------------------------------------------------------' ) print(yellow(" Looks like you need to get an API Key. ")) print(yellow(" The WARden comes with a shared key that")) print(yellow(" eventually gets to the call limit.")) print( ' -----------------------------------------------------------------' ) print( yellow( ' Go to: https://www.cryptocompare.com/cryptopian/api-keys' )) print( yellow( ' To get an API Key. Keys from cryptocompare are free.' )) print( yellow( ' [Tip] Get a disposable email to signup and protect privacy.' )) print( yellow( ' Services like https://temp-mail.org/en/ work well.' )) print(muted(" Current API:")) print(f" {api_key}") new_key = input(' Enter new API key (Q to quit): ') if new_key == 'Q' or new_key == 'q': exit() pickle_it('save', 'cryptocompare_api.pkl', new_key) check_cryptocompare() except KeyError: try: btc_price = (data['DISPLAY']['BTC']['USD']['PRICE']) spinner.ok("✅ ") spinner.write(success(f"BTC price is: {btc_price}")) pickle_it('save', 'cryptocompare_api.pkl', api_key) return except Exception: spinner.text = ( warning("CryptoCompare Returned an UNKNOWN error")) spinner.fail("💥 ") return (data)
def data_btc_price(): from node_warden import launch_logger launch_logger() from node_warden import load_config config = load_config(quiet=True) fx_config = config['CURRENCIES'] currencies = ast.literal_eval(fx_config.get('fx_list')) primary_fx = ast.literal_eval(fx_config.get('primary_fx')) price_data = multiple_price_grab('BTC', ','.join(currencies)) # Get prices in different currencies tabs = [] btc_usd_price = 0 for fx in currencies: try: price_str = price_data['DISPLAY']['BTC'][fx]['PRICE'] chg_str = price_data['DISPLAY']['BTC'][fx]['CHANGEPCTDAY'] high = price_data['DISPLAY']['BTC'][fx]['HIGHDAY'] low = price_data['DISPLAY']['BTC'][fx]['LOWDAY'] market = muted(price_data['DISPLAY']['BTC'][fx]['LASTMARKET']) try: chg = float(chg_str) if chg >= 0: chg_str = success('+' + chg_str + '%') elif chg < 0: chg_str = error(chg_str + '%') except Exception: chg_str = muted(chg_str + '%') if fx == 'USD': btc_usd_price = cleanfloat(price_str) if fx == primary_fx: fx = info(fx) tabs.append( [u' ' + fx, price_str, chg_str, low + ' - ' + high, market]) except Exception as e: tabs.append(['error: ' + str(e)]) tabs = tabulate( tabs, headers=['Fiat', 'Price', '% change', '24h Range', 'Source'], colalign=["center", "right", "right", "center", "right"]) # GBTC gbtc_config = config['STOCKS'] try: if gbtc_config.getboolean('GBTC_enabled'): tabs += '\n\n' gbtc_url = 'https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=GBTC&apikey=DDC232JDH' gbtc_data = tor_request(gbtc_url).json()['Global Quote'] gbtc_tabs = [] GBTC_shares = gbtc_config.getfloat('gbtc_shares') fairvalue, premium = (GBTC_premium(float(gbtc_data['05. price']), btc_usd_price, GBTC_shares)) if premium * 1 > 0: premium = success('+' + jformat(premium, 2, 0.01) + '%') elif premium * 1 < 0: premium = error(jformat(premium, 2, 0.01) + '%') fairvalue = jformat(fairvalue, 2) chg_str = gbtc_data['10. change percent'] try: chg = cleanfloat(chg_str) if chg > 0: chg_str = success('+' + jformat(chg, 2) + ' %') elif chg < 0: chg_str = error(jformat(chg, 2) + ' %') except Exception: chg_str = muted(chg_str) gbtc_tabs.append([ 'GBTC', gbtc_data['05. price'], chg_str, gbtc_data['04. low'] + ' - ' + gbtc_data['03. high'], premium, fairvalue, gbtc_data['07. latest trading day'] ]) gbtc_tabs = tabulate(gbtc_tabs, headers=[ 'Ticker', 'Price', '% change', '24h Range', 'Premium', 'Fair Value', 'Last Update' ], colalign=[ "center", "right", "right", "center", "right", "right", "right" ]) tabs += gbtc_tabs except Exception as e: er_st = error(f' Error getting GBTC data: {e}') tabs += er_st tabs += ( f"\n\n Last Refresh on: {info(datetime.now().strftime('%H:%M:%S'))}") return tabs
def data_mempool(): from node_warden import load_config from node_warden import launch_logger launch_logger() config = load_config(quiet=True) mp_config = config['MEMPOOL'] url = mp_config.get('url') tabs = [] # Get recommended fees mp_fee = tor_request(url + '/api/v1/fees/recommended').json() tabs = list(mp_fee.values()) tabs = [[str(x) + ' sats/Vb' for x in tabs]] tabs = tabulate(tabs, headers=["Fastest Fee", "30 min fee", "1 hour fee"], colalign=["center", "center", "center"]) try: block_height = tor_request(url + '/api/blocks/tip/height').json() except Exception: return (error(f' >> Error getting data from {url}. Retrying...')) # Save the latest block height saved_block = pickle_it(action='load', filename='block.pkl') if (saved_block != block_height) and ( config['MEMPOOL'].getboolean('block_found_sound')): # Block found play sound try: engine = pyttsx3.init() engine.setProperty('rate', 270) engine.say(config['MEMPOOL'].get('block_found_txt')) engine.runAndWait() except Exception: pass logging.info( info('[MEMPOOL] ') + success("A new Bitcoin Block was just found. ") + yellow("'Tick. Tock. Next block.'")) pickle_it(action='save', filename='block.pkl', data=block_height) block_txt = success(f' Block Height: {jformat(block_height, 0)}\n\n') tabs = block_txt + info(' Mempool Fee Estimates: \n') + tabs try: mp_blocks = tor_request(url + '/api/blocks').json() except Exception: return (error(" >> Error getting Mempool data. Retrying...")) mp_tabs = [] gradient_color = 0 for block in mp_blocks: mp_tabs.append([ time_ago(block['timestamp']), jformat(block['height'], 0), jformat(block['tx_count'], 0), jformat(block['size'], 2, 1000000) + ' MB' ]) gradient_color += 1 mp_tabs = tabulate(mp_tabs, headers=[" Time", "Height", "Tx Count", "Size"], colalign=["right", "center", "center", "right"]) tabs += info('\n\n Latest Blocks: \n') + mp_tabs tabs += muted(f"\n\n Source: {url} {success('[Tor Request]')}\n") return tabs
def data_btc_price(): from node_warden import launch_logger launch_logger() from node_warden import load_config config = load_config(quiet=True) fx_config = config['CURRENCIES'] currencies = ast.literal_eval(fx_config.get('fx_list')) primary_fx = ast.literal_eval(fx_config.get('primary_fx')) price_data = multiple_price_grab('BTC', ','.join(currencies)) # Get prices in different currencies tabs = [] btc_usd_price = 0 for fx in currencies: try: price_str = price_data['DISPLAY']['BTC'][fx]['PRICE'] chg_str = price_data['DISPLAY']['BTC'][fx]['CHANGEPCTDAY'] high = price_data['DISPLAY']['BTC'][fx]['HIGHDAY'] low = price_data['DISPLAY']['BTC'][fx]['LOWDAY'] market = muted(price_data['DISPLAY']['BTC'][fx]['LASTMARKET']) try: chg = float(chg_str) if chg >= 0: chg_str = success('+' + chg_str + '%') elif chg < 0: chg_str = error(chg_str + '%') except Exception: chg_str = muted(chg_str + '%') if fx == 'USD': btc_usd_price = cleanfloat(price_str) if fx == primary_fx: fx = info(fx) tabs.append( [u' ' + fx, price_str, chg_str, low + ' - ' + high, market]) except Exception as e: tabs.append(['error: ' + str(e)]) if tabs == []: return ( error(f' >> Error getting data from CryptoCompare. Retrying...')) try: tabs = tabulate( tabs, headers=['Fiat', 'Price', '% change', '24h Range', 'Source'], colalign=["center", "right", "right", "center", "right"]) except Exception: return ( error(f' >> Error getting data from CryptoCompare. Retrying...')) # GBTC gbtc_config = config['STOCKS'] try: if gbtc_config.getboolean('GBTC_enabled'): tabs += '\n\n' gbtc_url = 'https://finnhub.io/api/v1/quote?symbol=GBTC&token=bvfhuqv48v6rhdtvnks0' gbtc_data = tor_request(gbtc_url).json() gbtc_tabs = [] GBTC_shares = gbtc_config.getfloat('gbtc_shares') fairvalue, premium = (GBTC_premium((gbtc_data['c']), btc_usd_price, GBTC_shares)) if premium * 1 > 0: premium = success('+' + jformat(premium, 2, 0.01) + '%') elif premium * 1 < 0: premium = error(jformat(premium, 2, 0.01) + '%') fairvalue = jformat(fairvalue, 2) chg_str = gbtc_data['c'] / gbtc_data['pc'] try: chg = (chg_str) if chg > 0: chg_str = success('+' + jformat(chg, 2) + ' %') elif chg < 0: chg_str = error(jformat(chg, 2) + ' %') except Exception: chg_str = muted(chg_str) gbtc_tabs.append([ 'GBTC', jformat(gbtc_data['c'], 2), chg_str, jformat(gbtc_data['l'], 2) + ' - ' + jformat(gbtc_data['h'], 2), premium, fairvalue, time_ago(gbtc_data['t']) ]) gbtc_tabs = tabulate(gbtc_tabs, headers=[ 'Ticker', 'Price', '% change', '24h Range', 'Premium', 'Fair Value', 'Last Update' ], colalign=[ "center", "right", "right", "center", "right", "right", "right" ]) tabs += gbtc_tabs except Exception as e: er_st = error(f' Error getting GBTC data: {e}') tabs += er_st tabs += ( f"\n\n Last Refresh on: {info(datetime.now().strftime('%H:%M:%S'))}") return tabs
def check_cryptocompare(): with yaspin(text=f"Testing price grab from Cryptocompare", color="green") as spinner: config = load_config(True) api_key = config['API'].get('cryptocompare') # tickers should be in comma sep string format like "BTC,ETH,LTC" and "USD,EUR" baseURL = ( "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=BTC" + "&tsyms=USD&api_key=" + api_key) try: request = tor_request(baseURL) except requests.exceptions.ConnectionError: spinner.fail("💥 ") spinner.write( warning(" Connection Error - check internet connection")) exit() data = request.json() try: if data['Response'] == 'Error': config_file = os.path.join(basedir, 'config.ini') spinner.fail("💥 ") spinner.write(warning(" CryptoCompare Returned an error")) print(" " + data['Message']) if data['Message'] == 'You are over your rate limit please upgrade your account!': # First try to get a random API KEY if config['API'].getboolean('random') is not True: print( " Looks like you are over the API Limit. Will try to generate a random API." ) size = 16 import binascii random_key = binascii.b2a_hex(os.urandom(size)) config['API']['random'] = 'True' config['API']['cryptocompare'] = str( random_key.decode("utf-8")) with open(config_file, 'w') as configfile: config.write(configfile) check_cryptocompare() return print( ' -----------------------------------------------------------------' ) print( yellow(" Looks like you need to get an API Key. ")) print( yellow(" The WARden comes with a shared key that")) print(yellow(" eventually gets to the call limit.")) print( ' -----------------------------------------------------------------' ) print( blue( ' Go to: https://www.cryptocompare.com/cryptopian/api-keys' )) print(muted(" Current API:")) print(f" {api_key}") new_key = input(' Enter new API key (Q to quit): ') if new_key == 'Q' or new_key == 'q': exit() config['API']['cryptocompare'] = new_key with open(config_file, 'w') as configfile: config.write(configfile) check_cryptocompare() except KeyError: try: btc_price = (data['DISPLAY']['BTC']['USD']['PRICE']) spinner.ok("✅ ") spinner.write(success(f" BTC price is: {btc_price}")) return except Exception: spinner.fail("💥 ") spinner.write( warning(" CryptoCompare Returned an UNKNOWN error")) print(data) return (data)
def main_dashboard(config, tor, spinner): try: refresh_interval = int(config['MAIN'].get('refresh')) except Exception: refresh_interval = 5 running_jobs = { 'btc': { 'workers': 0 }, 'tor': { 'workers': 0 }, 'login': { 'workers': 0 }, 'mp': { 'workers': 0 }, 'logger': { 'workers': 0 } } palette = [('titlebar', 'dark green', ''), ('refresh button', 'dark green,bold', ''), ('quit button', 'dark green', ''), ('getting quote', 'dark blue', ''), ('headers', 'white,bold', ''), ('change ', 'dark green', ''), ('change negative', 'dark red', '')] def exit_on_q(key): if key in ('q', 'Q'): raise urwid.ExitMainLoop() def update_header(layout, message=None, message_type=None): # Create Header refresh_time = datetime.now().strftime('%H:%M:%S') txt = u' WARden Node Edition (Version: ' + version( ) + ') | Last Refresh on: ' + refresh_time if message: txt += ' | ' + message header_text = urwid.Text(txt) header = urwid.AttrMap(header_text, 'titlebar') layout.header = header # Draw empty dashboard spinner.stop() menu = urwid.Text([u'Press (', ('quit button', u'Q'), u') to quit.']) # Class to Create URWID box window to receive data class Box: def __init__(self, loader_text=None, valign='top', top=1, bottom=1, left=1, right=1, height=None): self.loader_text = loader_text self.valign = valign self.top = top self.bottom = bottom self.left = left self.right = right self.height = height self.text = urwid.Text(self.loader_text) self.filler = urwid.Filler(self.text, valign=self.valign, top=self.top, bottom=self.bottom) self.v_padding = urwid.Padding(self.filler, left=self.left, right=self.right) self.line_box = urwid.LineBox(self.v_padding) self.box_size = self.height # Create the BTC price box quote_box_size = len(ast.literal_eval( config['CURRENCIES'].get('fx_list'))) + 10 quote_box = Box(loader_text='Loading Prices...', height=quote_box_size).line_box # Create the TOR Box tor_box_size = 9 tor_box = Box(loader_text='Checking Tor Status...', height=tor_box_size).line_box # Create user login Box login_box_size = 12 login_box = Box(loader_text='Loading User Logins...', height=login_box_size).line_box # Create MemPool Box mp_box_size = 24 mp_box = Box(loader_text='Loading Mempool...', height=mp_box_size).line_box # Create Logger Box logger_box_size = 10 logger_box = Box(loader_text='Loading Message Log...', height=logger_box_size).line_box # Create the Satoshi Quotes Box satoshi_box_size = 20 satoshi_box = Box(loader_text='Loading Satoshi Wisdom...', height=satoshi_box_size).line_box # Assemble the widgets header = 'Loading...' log_tor = urwid.Columns([mp_box, urwid.Pile([login_box, tor_box])]) log_tor_size = max(mp_box_size, login_box_size, tor_box_size) bottom_box_size = max(satoshi_box_size, logger_box_size) bottom_box = urwid.Columns([logger_box, satoshi_box]) body_widget = urwid.Pile([(quote_box_size, quote_box), (log_tor_size, log_tor), (bottom_box_size, bottom_box)]) layout = urwid.Frame(header=header, body=body_widget, footer=menu) update_header(layout) # Handle key presses def handle_input(key): if key == 'R' or key == 'r': refresh(main_loop, '') if key == 'Q' or key == 'q': raise urwid.ExitMainLoop() def check_for_pump(_loop, _data): try: btc = btc_price_data() btc_price = btc['DISPLAY']['BTC']['USD']['PRICE'] chg_str = btc['DISPLAY']['BTC']['USD']['CHANGEPCTDAY'] chg = cleanfloat(chg_str) if chg > 5: logging.info( info("[NGU] ") + muted(f"Looks like Bitcoin is pumping ") + emoji.emojize(":rocket:") + yellow(f' {btc_price}') + success(f' +{chg_str}%')) if chg < -5: logging.info( info("[NGU] ") + muted( f"Looks like Bitcoin is dropping. Time to stack some sats. " ) + yellow(f' {btc_price}') + error(f' {chg_str}%')) except Exception: pass main_loop.set_alarm_in(300, check_for_pump) def get_quote(_loop, _data): quote = translate_text_for_urwid(data_random_satoshi()) satoshi_box.base_widget.set_text(quote) main_loop.set_alarm_in(60, get_quote) def refresh(_loop, _data): # Add Background Tasks update_header(layout) main_loop.draw_screen() # Add Background Updates max_workers = 1 # Max number of threads **KEEP AT 1*** # Beware of increasing the max_workers - need to make sure # processes are killed for all workers - CODE IS NOT DOING THAT # right now and will lead to CPU usage blowing up # UPDATER FUNCTIONS - ONE NEEDED PER UPDATE # These run on background as watch pipes def update_btc(read_data): read_data = translate_text_for_urwid(read_data) quote_box.base_widget.set_text(read_data) main_loop.remove_watch_pipe = True running_jobs['btc']['workers'] = 0 for pipe in running_jobs['btc']['pipe']: if pipe != []: pipe.kill() gc.collect() def update_tor(read_data): read_data = translate_text_for_urwid(read_data) tor_box.base_widget.set_text(read_data) running_jobs['tor']['workers'] = 0 for pipe in running_jobs['tor']['pipe']: if pipe != []: pipe.kill() gc.collect() def update_login(read_data): read_data = translate_text_for_urwid(read_data) login_box.base_widget.set_text(read_data) running_jobs['login']['workers'] = 0 for pipe in running_jobs['login']['pipe']: if pipe != []: pipe.kill() gc.collect() def update_mp(read_data): read_data = translate_text_for_urwid(read_data) mp_box.base_widget.set_text(read_data) main_loop.remove_watch_pipe = True running_jobs['mp']['workers'] = 0 for pipe in running_jobs['mp']['pipe']: if pipe != []: pipe.kill() gc.collect() def update_logger(read_data): read_data = translate_text_for_urwid(read_data) logger_box.base_widget.set_text(read_data) main_loop.remove_watch_pipe = True running_jobs['logger']['workers'] = 0 for pipe in running_jobs['logger']['pipe']: if pipe != []: pipe.kill() gc.collect() # Job List Dictionaty job_list = { 'btc': { 'max_workers': 1, 'subprocess': 'python3 data.py data_btc_price', 'updater': update_btc }, 'tor': { 'max_workers': 1, 'subprocess': 'python3 data.py data_tor', 'updater': update_tor }, 'login': { 'max_workers': 1, 'subprocess': 'python3 data.py data_login', 'updater': update_login }, 'mp': { 'max_workers': 1, 'subprocess': 'python3 data.py data_mempool', 'updater': update_mp }, 'logger': { 'max_workers': 1, 'subprocess': 'python3 data.py data_logger', 'updater': update_logger } } for job in job_list.keys(): if running_jobs[job]['workers'] < job_list[job]['max_workers']: running_jobs[job]['workers'] += 1 stdout = main_loop.watch_pipe(job_list[job]['updater']) stderr = main_loop.watch_pipe(job_list[job]['updater']) launch_process = subprocess.Popen(job_list[job]['subprocess'], shell=True, stdout=stdout, stderr=stderr) # Store or create a list to store running_jobs[job].setdefault('pipe', []).append(launch_process) main_loop.set_alarm_in(refresh_interval, refresh) try: main_loop = urwid.MainLoop(layout, palette, unhandled_input=handle_input) main_loop.set_alarm_in(30, check_for_pump) main_loop.set_alarm_in(10, get_quote) main_loop.set_alarm_in(0, refresh) main_loop.run() except Exception as e: # Catch some timeouts - only once logging.error(info('[MAIN] ') + muted('Error: ') + yellow(str(e))) update_header(layout, message=f'Error: {e}') main_dashboard(config, tor, spinner)