def on_internet_available(ui, config, log): if config['twitter']['enabled'] and log.is_new() and log.handshakes > 0: try: import tweepy except ImportError: logging.error("Couldn't import tweepy") return logging.info("detected a new session and internet connectivity!") picture = '/dev/shm/pwnagotchi.png' ui.on_manual_mode(log) ui.update(force=True) ui.image().save(picture, 'png') ui.set('status', 'Tweeting...') ui.update(force=True) try: auth = tweepy.OAuthHandler(config['twitter']['consumer_key'], config['twitter']['consumer_secret']) auth.set_access_token(config['twitter']['access_token_key'], config['twitter']['access_token_secret']) api = tweepy.API(auth) tweet = Voice(lang=config['main']['lang']).on_log_tweet(log) api.update_with_media(filename=picture, status=tweet) log.save_session_id() logging.info("tweeted: %s" % tweet) except Exception as e: logging.exception("error while tweeting")
def on_internet_available(agent): config = agent.config() display = agent.view() last_session = agent.last_session if last_session.is_new() and last_session.handshakes > 0: try: import tweepy except ImportError: logging.error("Couldn't import tweepy") return logging.info("detected a new session and internet connectivity!") picture = '/dev/shm/pwnagotchi.png' display.on_manual_mode(last_session) display.update(force=True) display.image().save(picture, 'png') display.set('status', 'Tweeting...') display.update(force=True) try: auth = tweepy.OAuthHandler(OPTIONS['consumer_key'], OPTIONS['consumer_secret']) auth.set_access_token(OPTIONS['access_token_key'], OPTIONS['access_token_secret']) api = tweepy.API(auth) tweet = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session) api.update_with_media(filename=picture, status=tweet) last_session.save_session_id() logging.info("tweeted: %s" % tweet) except Exception as e: logging.exception("error while tweeting")
def on_internet_available(self, agent): config = agent.config() display = agent.view() last_session = agent.last_session if last_session.is_new() and last_session.handshakes > 0: try: import tweepy except ImportError: logging.error("[twitter] Couldn't import tweepy.") return logging.info("[twitter] Detected a new session and internet connectivity!") picture = '/var/tmp/pwnagotchi/pwnagotchi.png' if os.path.exists("/var/tmp/pwnagotchi/pwnagotchi.png") else '/root/pwnagotchi.png' display.on_manual_mode(last_session) display.update(force=True) display.image().save(picture, 'png') display.set('status', 'Tweeting...') display.update(force=True) try: auth = tweepy.OAuthHandler(self.options['consumer_key'], self.options['consumer_secret']) auth.set_access_token(self.options['access_token_key'], self.options['access_token_secret']) api = tweepy.API(auth) tweet = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session) api.update_with_media(filename=picture, status=tweet) last_session.save_session_id() logging.info(f"[twitter] Tweeted: {tweet}") except Exception as e: logging.exception(f"[twitter] Error while tweeting: {e}")
def on_internet_available(self, agent): if not self.ready: return config = agent.config() display = agent.view() last_session = agent.last_session if last_session.is_new() and last_session.handshakes > 0: try: import telegram except ImportError as e: logging.error("[telegram] Couldn't import python library.") logging.debug(e) return logging.info( "[telegram] Detected new activity and internet, time to send a message!" ) picture = '/var/tmp/pwnagotchi/pwnagotchi.png' if os.path.exists( "/var/tmp/pwnagotchi/pwnagotchi.png" ) else '/root/pwnagotchi.png' display.on_manual_mode(last_session) display.image().save(picture, 'png') display.update(force=True) try: logging.info("[telegram] Connecting to Telegram...") message = Voice(lang=config['main'] ['lang']).on_last_session_tweet(last_session) bot = telegram.Bot(self.options['bot_token']) if self.options['send_picture'] is True: logging.info("[telegram] Sending picture...") bot.sendPhoto(chat_id=self.options['chat_id'], photo=open(picture, 'rb')) logging.info("[telegram] Picture sent.") if self.options['send_message'] is True: logging.info("[telegram] Sending message...") bot.sendMessage(chat_id=self.options['chat_id'], text=message, disable_web_page_preview=True) logging.info(f"[telegram] Message sent: {message}") last_session.save_session_id() display.set('status', 'Telegram notification sent!') display.update(force=True) except Exception as e: logging.exception( f"[telegram] An error occurred in the Telegram plugin: {e}" ) display.set('face', faces.BROKEN) display.set('status', 'An error occured in the Telegram plugin.') display.update(force=True)
def __init__(self, config): self.config = config self.voice = Voice(lang=config['main']['lang']) self.path = config['main']['log']['path'] self.last_session = [] self.last_session_id = '' self.last_saved_session_id = '' self.duration = '' self.duration_human = '' self.deauthed = 0 self.associated = 0 self.handshakes = 0 self.epochs = 0 self.train_epochs = 0 self.min_reward = 1000 self.max_reward = -1000 self.avg_reward = 0 self.parsed = False
def on_internet_available(self, agent): config = agent.config() display = agent.view() last_session = agent.last_session api_base_url = self.options['instance_url'] email = self.options['email'] password = self.options['password'] visibility = self.options['visibility'] client_cred = '/root/.mastodon.client.secret' user_cred = '/root/.mastodon.user.secret' if last_session.is_new() and last_session.handshakes > 0: logging.info('[mastodon] Detected internet and new activity: time to post!') if not os.path.isfile(user_cred) or not os.path.isfile(client_cred): # Runs only if there are any missing credential files Mastodon.create_app( config['main']['name'], api_base_url=api_base_url, to_file=client_cred ) picture = '/root/pwnagotchi.png' display.on_manual_mode(last_session) display.image().save(picture, 'png') display.update(force=True) try: logging.info('[mastodon] Connecting to Mastodon API') mastodon = Mastodon( client_id=client_cred, api_base_url=api_base_url ) mastodon.log_in( email, password, to_file=user_cred ) mastodon = Mastodon( access_token=user_cred, api_base_url=api_base_url ) message = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session) mastodon.status_post( message, media_ids=mastodon.media_post(picture), visibility=visibility ) last_session.save_session_id() logging.info('[mastodon] posted: %s', message) display.set('status', 'Posted!') display.update(force=True) except Exception as ex: logging.exception('[mastodon] error while posting: %s', ex)
def __init__(self, config): self.config = config self.voice = Voice(lang=config['main']['lang']) self.path = config['main']['log'] self.last_session = [] self.last_session_id = '' self.last_saved_session_id = '' self.duration = '' self.duration_human = '' self.deauthed = 0 self.associated = 0 self.handshakes = 0 self.peers = 0 self.last_peer = None self.epochs = 0 self.train_epochs = 0 self.min_reward = 1000 self.max_reward = -1000 self.avg_reward = 0 self._peer_parser = re.compile( 'detected unit (.+)@(.+) \(v.+\) on channel \d+ \(([\d\-]+) dBm\) \[sid:(.+) pwnd_tot:(\d+) uptime:(\d+)\]') self.parsed = False
def on_internet_available(self, agent): if not self.ready: return config = agent.config() display = agent.view() last_session = agent.last_session if last_session.is_new() and last_session.handshakes > 0: try: from discord import Webhook, RequestsWebhookAdapter, File except ImportError as e: logging.error("Discord: couldn't import discord.py") logging.debug(e) return logging.info( "Discord: detected new activity and internet, time to send a message!" ) picture = '/var/tmp/pwnagotchi/pwnagotchi.png' if os.path.exists( "/var/tmp/pwnagotchi/pwnagotchi.png" ) else '/root/pwnagotchi.png' display.on_manual_mode(last_session) display.image().save(picture, 'png') display.update(force=True) try: logging.info("Discord: sending message...") message = Voice(lang=config['main'] ['lang']).on_last_session_tweet(last_session) url = self.options['webhook_url'] username = self.options['username'] webhook = Webhook.from_url(url, adapter=RequestsWebhookAdapter()) webhook.send(message, username=username, file=File(picture)) logging.info("Discord: message sent: %s" % message) last_session.save_session_id() display.set('status', 'Discord notification sent!') display.update(force=True) except Exception as e: logging.exception("Discord: error while sending message") logging.debug(e)
def on_internet_available(self, agent): config = agent.config() display = agent.view() last_session = agent.last_session if last_session.is_new() and last_session.handshakes > 0: try: import telegram except ImportError: logging.error('[telegram] Couldn\'t import telegram') return logging.info( '[telegram] Detected new activity and internet, time to send a message!' ) picture = '/root/pwnagotchi.png' display.on_manual_mode(last_session) display.image().save(picture, 'png') display.update(force=True) try: logging.info('[telegram] Connecting to Telegram...') message = Voice(lang=config['main'] ['lang']).on_last_session_tweet(last_session) bot = telegram.Bot(self.options['bot_token']) if self.options['send_picture'] is True: bot.sendPhoto(chat_id=self.options['chat_id'], photo=open(picture, 'rb')) logging.info('[telegram] picture sent') if self.options['send_message'] is True: bot.sendMessage(chat_id=self.options['chat_id'], text=message, disable_web_page_preview=True) logging.info('[telegram] message sent: %s', message) last_session.save_session_id() display.set('status', 'Telegram notification sent!') display.update(force=True) except Exception as ex: logging.exception( '[telegram] Error while sending on Telegram: %s', ex)
def __init__(self, config): self.config = config self.voice = Voice(lang=config['main']['lang']) self.path = config['main']['log'] self.last_session = None self.last_session_id = '' self.last_saved_session_id = '' self.duration = '' self.duration_human = '' self.deauthed = 0 self.associated = 0 self.handshakes = 0 self.peers = 0 self.last_peer = None self._peer_parser = re.compile( 'detected unit (.+)@(.+) \(v.+\) on channel \d+ \(([\d\-]+) dBm\) \[sid:(.+) pwnd_tot:(\d+) uptime:(\d+)\]' ) lines = [] if os.path.exists(self.path): with FileReadBackwards(self.path, encoding="utf-8") as fp: for line in fp: line = line.strip() if line != "" and line[0] != '[': continue lines.append(line) if SessionParser.START_TOKEN in line: break lines.reverse() if len(lines) == 0: lines.append("Initial Session") self.last_session = lines self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest() self.last_saved_session_id = self._get_last_saved_session_id() self._parse_stats()
def on_internet_available(self, agent): config = agent.config() display = agent.view() last_session = agent.last_session if last_session.is_new() and last_session.handshakes > 0: try: import tweepy except ImportError as ie: logging.error('[twitter] Couldn\'t import tweepy (%s)', ie) return logging.info( '[twitter] detected a new session and internet connectivity!') picture = '/root/pwnagotchi.png' display.on_manual_mode(last_session) with display.block_update(force=True): display.image().save(picture, 'png') display.set('status', 'Tweeting...') display.update(force=True) try: auth = tweepy.OAuthHandler(self.options['consumer_key'], self.options['consumer_secret']) auth.set_access_token(self.options['access_token_key'], self.options['access_token_secret']) api = tweepy.API(auth) tweet = Voice(lang=config['main'] ['lang']).on_last_session_tweet(last_session) api.update_with_media(filename=picture, status=tweet) last_session.save_session_id() logging.info('[twitter] tweeted: %s', tweet) except Exception as e: logging.exception('[twitter] error while tweeting (%s)', e)
def __init__(self, config, impl, state=None): global ROOT # setup faces from the configuration in case the user customized them faces.load_from_config(config['ui']['faces']) self._agent = None self._render_cbs = [] self._config = config self._canvas = None self._frozen = False self._lock = Lock() self._voice = Voice(lang=config['main']['lang']) self._implementation = impl self._layout = impl.layout() self._width = self._layout['width'] self._height = self._layout['height'] self._state = State(state={ 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=self._layout['channel'], label_font=fonts.Bold, text_font=fonts.Medium), 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=self._layout['aps'], label_font=fonts.Bold, text_font=fonts.Medium), 'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=self._layout['uptime'], label_font=fonts.Bold, text_font=fonts.Medium), 'line1': Line(self._layout['line1'], color=BLACK), 'line2': Line(self._layout['line2'], color=BLACK), 'face': Text(value=faces.SLEEP, position=self._layout['face'], color=BLACK, font=fonts.Huge), 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK), 'friend_name': Text(value=None, position=self._layout['friend_name'], font=fonts.BoldSmall, color=BLACK), 'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold), 'status': Text(value=self._voice.default(), position=self._layout['status']['pos'], color=BLACK, font=self._layout['status']['font'], wrap=True, # the current maximum number of characters per line, assuming each character is 6 pixels wide max_length=self._layout['status']['max']), 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, position=self._layout['shakes'], label_font=fonts.Bold, text_font=fonts.Medium), 'mode': Text(value='AUTO', position=self._layout['mode'], font=fonts.Bold, color=BLACK), }) if state: for key, value in state.items(): self._state.set(key, value) plugins.on('ui_setup', self) if config['ui']['fps'] > 0.0: _thread.start_new_thread(self._refresh_handler, ()) self._ignore_changes = () else: logging.warning("ui.fps is 0, the display will only update for major changes") self._ignore_changes = ('uptime', 'name') ROOT = self
class View: def __init__(self, config, impl, state=None): global ROOT # setup faces from the configuration in case the user customized them faces.load_from_config(config['ui']['faces']) self._agent = None self._render_cbs = [] self._config = config self._canvas = None self._frozen = False self._lock = Lock() self._voice = Voice(lang=config['main']['lang']) self._implementation = impl self._layout = impl.layout() self._width = self._layout['width'] self._height = self._layout['height'] self._state = State(state={ 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=self._layout['channel'], label_font=fonts.Bold, text_font=fonts.Medium), 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=self._layout['aps'], label_font=fonts.Bold, text_font=fonts.Medium), 'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=self._layout['uptime'], label_font=fonts.Bold, text_font=fonts.Medium), 'line1': Line(self._layout['line1'], color=BLACK), 'line2': Line(self._layout['line2'], color=BLACK), 'face': Text(value=faces.SLEEP, position=self._layout['face'], color=BLACK, font=fonts.Huge), 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK), 'friend_name': Text(value=None, position=self._layout['friend_name'], font=fonts.BoldSmall, color=BLACK), 'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold), 'status': Text(value=self._voice.default(), position=self._layout['status']['pos'], color=BLACK, font=self._layout['status']['font'], wrap=True, # the current maximum number of characters per line, assuming each character is 6 pixels wide max_length=self._layout['status']['max']), 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, position=self._layout['shakes'], label_font=fonts.Bold, text_font=fonts.Medium), 'mode': Text(value='AUTO', position=self._layout['mode'], font=fonts.Bold, color=BLACK), }) if state: for key, value in state.items(): self._state.set(key, value) plugins.on('ui_setup', self) if config['ui']['fps'] > 0.0: _thread.start_new_thread(self._refresh_handler, ()) self._ignore_changes = () else: logging.warning("ui.fps is 0, the display will only update for major changes") self._ignore_changes = ('uptime', 'name') ROOT = self def set_agent(self, agent): self._agent = agent def has_element(self, key): self._state.has_element(key) def add_element(self, key, elem): self._state.add_element(key, elem) def remove_element(self, key): self._state.remove_element(key) def width(self): return self._width def height(self): return self._height def on_state_change(self, key, cb): self._state.add_listener(key, cb) def on_render(self, cb): if cb not in self._render_cbs: self._render_cbs.append(cb) def _refresh_handler(self): delay = 1.0 / self._config['ui']['fps'] while True: try: name = self._state.get('name') self.set('name', name.rstrip('█').strip() if '█' in name else (name + ' █')) self.update() except Exception as e: logging.warning("non fatal error while updating view: %s" % e) time.sleep(delay) def set(self, key, value): self._state.set(key, value) def get(self, key): return self._state.get(key) def on_starting(self): self.set('status', self._voice.on_starting() + ("\n(v%s)" % pwnagotchi.__version__)) self.set('face', faces.AWAKE) self.update() def on_ai_ready(self): self.set('mode', ' AI') self.set('face', faces.HAPPY) self.set('status', self._voice.on_ai_ready()) self.update() def on_manual_mode(self, last_session): self.set('mode', 'MANU') self.set('face', faces.SAD if (last_session.epochs > 3 and last_session.handshakes == 0) else faces.HAPPY) self.set('status', self._voice.on_last_session_data(last_session)) self.set('epoch', "%04d" % last_session.epochs) self.set('uptime', last_session.duration) self.set('channel', '-') self.set('aps', "%d" % last_session.associated) self.set('shakes', '%d (%s)' % (last_session.handshakes, \ utils.total_unique_handshakes(self._config['bettercap']['handshakes']))) self.update() def is_normal(self): return self._state.get('face') not in ( faces.INTENSE, faces.COOL, faces.BORED, faces.HAPPY, faces.EXCITED, faces.MOTIVATED, faces.DEMOTIVATED, faces.SMART, faces.SAD, faces.LONELY) def on_keys_generation(self): self.set('face', faces.AWAKE) self.set('status', self._voice.on_keys_generation()) self.update() def on_normal(self): self.set('face', faces.AWAKE) self.set('status', self._voice.on_normal()) self.update() def on_free_channel(self, channel): self.set('face', faces.SMART) self.set('status', self._voice.on_free_channel(channel)) self.update() def on_reading_logs(self, lines_so_far=0): self.set('face', faces.SMART) self.set('status', self._voice.on_reading_logs(lines_so_far)) self.update() def wait(self, secs, sleeping=True): was_normal = self.is_normal() part = secs / 10.0 for step in range(0, 10): # if we weren't in a normal state before going # to sleep, keep that face and status on for # a while, otherwise the sleep animation will # always override any minor state change before it if was_normal or step > 5: if sleeping: if secs > 1: self.set('face', faces.SLEEP) self.set('status', self._voice.on_napping(int(secs))) else: self.set('face', faces.SLEEP2) self.set('status', self._voice.on_awakening()) else: self.set('status', self._voice.on_waiting(int(secs))) good_mood = self._agent.in_good_mood() if step % 2 == 0: self.set('face', faces.LOOK_R_HAPPY if good_mood else faces.LOOK_R) else: self.set('face', faces.LOOK_L_HAPPY if good_mood else faces.LOOK_L) time.sleep(part) secs -= part self.on_normal() def on_shutdown(self): self.set('face', faces.SLEEP) self.set('status', self._voice.on_shutdown()) self.update(force=True) self._frozen = True def on_bored(self): self.set('face', faces.BORED) self.set('status', self._voice.on_bored()) self.update() def on_sad(self): self.set('face', faces.SAD) self.set('status', self._voice.on_sad()) self.update() def on_angry(self): self.set('face', faces.ANGRY) self.set('status', self._voice.on_angry()) self.update() def on_motivated(self, reward): self.set('face', faces.MOTIVATED) self.set('status', self._voice.on_motivated(reward)) self.update() def on_demotivated(self, reward): self.set('face', faces.DEMOTIVATED) self.set('status', self._voice.on_demotivated(reward)) self.update() def on_excited(self): self.set('face', faces.EXCITED) self.set('status', self._voice.on_excited()) self.update() def on_assoc(self, ap): self.set('face', faces.INTENSE) self.set('status', self._voice.on_assoc(ap)) self.update() def on_deauth(self, sta): self.set('face', faces.COOL) self.set('status', self._voice.on_deauth(sta)) self.update() def on_miss(self, who): self.set('face', faces.SAD) self.set('status', self._voice.on_miss(who)) self.update() def on_lonely(self): self.set('face', faces.LONELY) self.set('status', self._voice.on_lonely()) self.update() def on_handshakes(self, new_shakes): self.set('face', faces.HAPPY) self.set('status', self._voice.on_handshakes(new_shakes)) self.update() def on_unread_messages(self, count, total): self.set('face', faces.EXCITED) self.set('status', self._voice.on_unread_messages(count, total)) self.update() time.sleep(5.0) def on_rebooting(self): self.set('face', faces.BROKEN) self.set('status', self._voice.on_rebooting()) self.update() def on_custom(self, text): self.set('face', faces.DEBUG) self.set('status', self._voice.custom(text)) self.update() @contextmanager def block_update(self, *args, **kwargs): self._lock.acquire() try: self.update(*args, with_lock=False, **kwargs) yield self finally: self._lock.release() def update(self, force=False, new_data={}, with_lock=True): for key, val in new_data.items(): self.set(key, val) maybe_lock = self._lock if with_lock else nullcontext() with maybe_lock: if self._frozen: return state = self._state changes = state.changes(ignore=self._ignore_changes) min_changes = 2 if self._config['ui']['fps'] == 0.0 else 0 if force or len(changes) > min_changes: logging.debug("Update screen because %s", 'it was forced.' if force else f"{changes} triggered it.") self._canvas = Image.new('1', (self._width, self._height), WHITE) drawer = ImageDraw.Draw(self._canvas) plugins.on('ui_update', self) for key, lv in state.items(): lv.draw(self._canvas, drawer) if self._config['ui']['web']['dark']: print(self._canvas.mode) self._canvas = ImageOps.invert(self._canvas.convert('L')).convert('1') web.update_frame(self._canvas) for cb in self._render_cbs: cb(self._canvas) self._state.reset()
def __init__(self, config, state={}): self._render_cbs = [] self._config = config self._canvas = None self._lock = Lock() self._voice = Voice(lang=config['main']['lang']) self._width, self._height, \ face_pos, name_pos, status_pos = setup_display_specifics(config) self._state = State( state={ 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=(0, 0), label_font=fonts.Bold, text_font=fonts.Medium), 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=(30, 0), label_font=fonts.Bold, text_font=fonts.Medium), # 'epoch': LabeledValue(color=BLACK, label='E', value='0000', position=(145, 0), label_font=fonts.Bold, # text_font=fonts.Medium), 'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=(self._width - 65, 0), label_font=fonts.Bold, text_font=fonts.Medium), 'line1': Line([ 0, int(self._height * .12), self._width, int(self._height * .12) ], color=BLACK), 'line2': Line([ 0, self._height - int(self._height * .12), self._width, self._height - int(self._height * .12) ], color=BLACK), 'face': Text(value=faces.SLEEP, position=face_pos, color=BLACK, font=fonts.Huge), 'friend_face': Text( value=None, position=(0, 90), font=fonts.Bold, color=BLACK), 'friend_name': Text(value=None, position=(40, 93), font=fonts.BoldSmall, color=BLACK), 'name': Text(value='%s>' % 'pwnagotchi', position=name_pos, color=BLACK, font=fonts.Bold), 'status': Text( value=self._voice.default(), position=status_pos, color=BLACK, font=fonts.Medium, wrap=True, # the current maximum number of characters per line, assuming each character is 6 pixels wide max_length=(self._width - status_pos[0]) // 6), 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, position=(0, self._height - int(self._height * .12) + 1), label_font=fonts.Bold, text_font=fonts.Medium), 'mode': Text(value='AUTO', position=(self._width - 25, self._height - int(self._height * .12) + 1), font=fonts.Bold, color=BLACK), }) for key, value in state.items(): self._state.set(key, value) plugins.on('ui_setup', self) if config['ui']['fps'] > 0.0: _thread.start_new_thread(self._refresh_handler, ()) self._ignore_changes = () else: logging.warning( "ui.fps is 0, the display will only update for major changes") self._ignore_changes = ('uptime', 'name')
class View(object): def __init__(self, config, state={}): self._render_cbs = [] self._config = config self._canvas = None self._lock = Lock() self._voice = Voice(lang=config['main']['lang']) self._width, self._height, \ face_pos, name_pos, status_pos = setup_display_specifics(config) self._state = State( state={ 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=(0, 0), label_font=fonts.Bold, text_font=fonts.Medium), 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=(30, 0), label_font=fonts.Bold, text_font=fonts.Medium), # 'epoch': LabeledValue(color=BLACK, label='E', value='0000', position=(145, 0), label_font=fonts.Bold, # text_font=fonts.Medium), 'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=(self._width - 65, 0), label_font=fonts.Bold, text_font=fonts.Medium), 'line1': Line([ 0, int(self._height * .12), self._width, int(self._height * .12) ], color=BLACK), 'line2': Line([ 0, self._height - int(self._height * .12), self._width, self._height - int(self._height * .12) ], color=BLACK), 'face': Text(value=faces.SLEEP, position=face_pos, color=BLACK, font=fonts.Huge), 'friend_face': Text( value=None, position=(0, 90), font=fonts.Bold, color=BLACK), 'friend_name': Text(value=None, position=(40, 93), font=fonts.BoldSmall, color=BLACK), 'name': Text(value='%s>' % 'pwnagotchi', position=name_pos, color=BLACK, font=fonts.Bold), 'status': Text( value=self._voice.default(), position=status_pos, color=BLACK, font=fonts.Medium, wrap=True, # the current maximum number of characters per line, assuming each character is 6 pixels wide max_length=(self._width - status_pos[0]) // 6), 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, position=(0, self._height - int(self._height * .12) + 1), label_font=fonts.Bold, text_font=fonts.Medium), 'mode': Text(value='AUTO', position=(self._width - 25, self._height - int(self._height * .12) + 1), font=fonts.Bold, color=BLACK), }) for key, value in state.items(): self._state.set(key, value) plugins.on('ui_setup', self) if config['ui']['fps'] > 0.0: _thread.start_new_thread(self._refresh_handler, ()) self._ignore_changes = () else: logging.warning( "ui.fps is 0, the display will only update for major changes") self._ignore_changes = ('uptime', 'name') def add_element(self, key, elem): self._state.add_element(key, elem) def width(self): return self._width def height(self): return self._height def on_state_change(self, key, cb): self._state.add_listener(key, cb) def on_render(self, cb): if cb not in self._render_cbs: self._render_cbs.append(cb) def _refresh_handler(self): delay = 1.0 / self._config['ui']['fps'] # logging.info("view refresh handler started with period of %.2fs" % delay) while True: name = self._state.get('name') self.set( 'name', name.rstrip('█').strip() if '█' in name else (name + ' █')) self.update() time.sleep(delay) def set(self, key, value): self._state.set(key, value) def on_starting(self): self.set('status', self._voice.on_starting()) self.set('face', faces.AWAKE) def on_ai_ready(self): self.set('mode', '') self.set('face', faces.HAPPY) self.set('status', self._voice.on_ai_ready()) self.update() def on_manual_mode(self, log): self.set('mode', 'MANU') self.set('face', faces.SAD if log.handshakes == 0 else faces.HAPPY) self.set('status', self._voice.on_log(log)) self.set('epoch', "%04d" % log.epochs) self.set('uptime', log.duration) self.set('channel', '-') self.set('aps', "%d" % log.associated) self.set('shakes', '%d (%s)' % (log.handshakes, \ core.total_unique_handshakes(self._config['bettercap']['handshakes']))) self.set_closest_peer(log.last_peer) def is_normal(self): return self._state.get('face') not in (faces.INTENSE, faces.COOL, faces.BORED, faces.HAPPY, faces.EXCITED, faces.MOTIVATED, faces.DEMOTIVATED, faces.SMART, faces.SAD, faces.LONELY) def on_normal(self): self.set('face', faces.AWAKE) self.set('status', self._voice.on_normal()) self.update() def set_closest_peer(self, peer): if peer is None: self.set('friend_face', None) self.set('friend_name', None) else: # ref. https://www.metageek.com/training/resources/understanding-rssi-2.html if peer.rssi >= -67: num_bars = 4 elif peer.rssi >= -70: num_bars = 3 elif peer.rssi >= -80: num_bars = 2 else: num_bars = 1 name = '▌' * num_bars name += '│' * (4 - num_bars) name += ' %s %d (%d)' % (peer.name(), peer.pwnd_run(), peer.pwnd_total()) self.set('friend_face', peer.face()) self.set('friend_name', name) self.update() def on_new_peer(self, peer): self.set('face', faces.FRIEND) self.set('status', self._voice.on_new_peer(peer)) self.update() def on_lost_peer(self, peer): self.set('face', faces.LONELY) self.set('status', self._voice.on_lost_peer(peer)) self.update() def on_free_channel(self, channel): self.set('face', faces.SMART) self.set('status', self._voice.on_free_channel(channel)) self.update() def wait(self, secs, sleeping=True): was_normal = self.is_normal() part = secs / 10.0 for step in range(0, 10): # if we weren't in a normal state before goin # to sleep, keep that face and status on for # a while, otherwise the sleep animation will # always override any minor state change before it if was_normal or step > 5: if sleeping: if secs > 1: self.set('face', faces.SLEEP) self.set('status', self._voice.on_napping(int(secs))) else: self.set('face', faces.SLEEP2) self.set('status', self._voice.on_awakening()) else: self.set('status', self._voice.on_waiting(int(secs))) if step % 2 == 0: self.set('face', faces.LOOK_R) else: self.set('face', faces.LOOK_L) time.sleep(part) secs -= part self.on_normal() def on_bored(self): self.set('face', faces.BORED) self.set('status', self._voice.on_bored()) self.update() def on_sad(self): self.set('face', faces.SAD) self.set('status', self._voice.on_sad()) self.update() def on_motivated(self, reward): self.set('face', faces.MOTIVATED) self.set('status', self._voice.on_motivated(reward)) self.update() def on_demotivated(self, reward): self.set('face', faces.DEMOTIVATED) self.set('status', self._voice.on_demotivated(reward)) self.update() def on_excited(self): self.set('face', faces.EXCITED) self.set('status', self._voice.on_excited()) self.update() def on_assoc(self, ap): self.set('face', faces.INTENSE) self.set('status', self._voice.on_assoc(ap)) self.update() def on_deauth(self, sta): self.set('face', faces.COOL) self.set('status', self._voice.on_deauth(sta)) self.update() def on_miss(self, who): self.set('face', faces.SAD) self.set('status', self._voice.on_miss(who)) self.update() def on_lonely(self): self.set('face', faces.LONELY) self.set('status', self._voice.on_lonely()) self.update() def on_handshakes(self, new_shakes): self.set('face', faces.HAPPY) self.set('status', self._voice.on_handshakes(new_shakes)) self.update() def on_rebooting(self): self.set('face', faces.BROKEN) self.set('status', self._voice.on_rebooting()) self.update() def on_custom(self, text): self.set('face', faces.DEBUG) self.set('status', self._voice.custom(text)) self.update() def update(self, force=False): with self._lock: changes = self._state.changes(ignore=self._ignore_changes) if force or len(changes): self._canvas = Image.new('1', (self._width, self._height), WHITE) drawer = ImageDraw.Draw(self._canvas) plugins.on('ui_update', self) for key, lv in self._state.items(): lv.draw(self._canvas, drawer) for cb in self._render_cbs: cb(self._canvas) self._state.reset()
picture = '/dev/shm/pwnagotchi.png' display.update() display.image().save(picture, 'png') display.set('status', 'Tweeting...') display.update() try: auth = tweepy.OAuthHandler( config['twitter']['consumer_key'], config['twitter']['consumer_secret']) auth.set_access_token(config['twitter']['access_token_key'], config['twitter']['access_token_secret']) api = tweepy.API(auth) tweet = Voice(lang=config['main']['lang']).on_log_tweet(log) api.update_with_media(filename=picture, status=tweet) log.save_session_id() core.log("tweeted: %s" % tweet) except Exception as e: core.log("error: %s" % e) quit() core.logfile = config['main']['log'] agent.start_ai() agent.setup_events() agent.set_ready() agent.start_monitor_mode()
class View(object): def __init__(self, config, impl, state=None): global ROOT # setup faces from the configuration in case the user customized them faces.load_from_config(config['ui']['faces']) self._agent = None self._render_cbs = [] self._config = config self._canvas = None self._frozen = False self._lock = Lock() self._voice = Voice(lang=config['main']['lang']) self._implementation = impl self._layout = impl.layout() self._width = self._layout['width'] self._height = self._layout['height'] self._state = State( state={ 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=self._layout['channel'], label_font=fonts.Bold, text_font=fonts.Medium), 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=self._layout['aps'], label_font=fonts.Bold, text_font=fonts.Medium), 'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=self._layout['uptime'], label_font=fonts.Bold, text_font=fonts.Medium), 'line1': Line(self._layout['line1'], color=BLACK), 'line2': Line(self._layout['line2'], color=BLACK), 'face': Text(value=faces.SLEEP, position=self._layout['face'], color=BLACK, font=fonts.Huge), 'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK), 'friend_name': Text(value=None, position=self._layout['friend_name'], font=fonts.BoldSmall, color=BLACK), 'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold), 'status': Text( value=self._voice.default(), position=self._layout['status']['pos'], color=BLACK, font=self._layout['status']['font'], wrap=True, # the current maximum number of characters per line, assuming each character is 6 pixels wide max_length=self._layout['status']['max']), 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, position=self._layout['shakes'], label_font=fonts.Bold, text_font=fonts.Medium), 'mode': Text(value='AUTO', position=self._layout['mode'], font=fonts.Bold, color=BLACK), }) if state: for key, value in state.items(): self._state.set(key, value) plugins.on('ui_setup', self) if config['ui']['fps'] > 0.0: _thread.start_new_thread(self._refresh_handler, ()) self._ignore_changes = () else: logging.warning( "ui.fps is 0, the display will only update for major changes") self._ignore_changes = ('uptime', 'name') ROOT = self def set_agent(self, agent): self._agent = agent def has_element(self, key): self._state.has_element(key) def add_element(self, key, elem): self._state.add_element(key, elem) def remove_element(self, key): self._state.remove_element(key) def width(self): return self._width def height(self): return self._height def on_state_change(self, key, cb): self._state.add_listener(key, cb) def on_render(self, cb): if cb not in self._render_cbs: self._render_cbs.append(cb) def _refresh_handler(self): delay = 1.0 / self._config['ui']['fps'] # logging.info("view refresh handler started with period of %.2fs" % delay) while True: name = self._state.get('name') self.set( 'name', name.rstrip('█').strip() if '█' in name else (name + ' █')) self.update() time.sleep(delay) def set(self, key, value): self._state.set(key, value) def get(self, key): return self._state.get(key) def on_starting(self): self.set('status', self._voice.on_starting()) self.set('face', faces.AWAKE) def on_ai_ready(self): self.set('mode', ' AI') self.set('face', faces.HAPPY) self.set('status', self._voice.on_ai_ready()) self.update() def on_manual_mode(self, last_session): self.set('mode', 'MANU') self.set( 'face', faces.SAD if (last_session.epochs > 3 and last_session.handshakes == 0) else faces.HAPPY) self.set('status', self._voice.on_last_session_data(last_session)) self.set('epoch', "%04d" % last_session.epochs) self.set('uptime', last_session.duration) self.set('channel', '-') self.set('aps', "%d" % last_session.associated) self.set('shakes', '%d (%s)' % (last_session.handshakes, \ utils.total_unique_handshakes(self._config['bettercap']['handshakes']))) self.set_closest_peer(last_session.last_peer, last_session.peers) self.update() def is_normal(self): return self._state.get('face') not in (faces.INTENSE, faces.COOL, faces.BORED, faces.HAPPY, faces.EXCITED, faces.MOTIVATED, faces.DEMOTIVATED, faces.SMART, faces.SAD, faces.LONELY) def on_keys_generation(self): self.set('face', faces.AWAKE) self.set('status', self._voice.on_keys_generation()) self.update() def on_normal(self): self.set('face', faces.AWAKE) self.set('status', self._voice.on_normal()) self.update() def set_closest_peer(self, peer, num_total): if peer is None: self.set('friend_face', None) self.set('friend_name', None) else: # ref. https://www.metageek.com/training/resources/understanding-rssi-2.html if peer.rssi >= -67: num_bars = 4 elif peer.rssi >= -70: num_bars = 3 elif peer.rssi >= -80: num_bars = 2 else: num_bars = 1 name = '▌' * num_bars name += '│' * (4 - num_bars) name += ' %s %d (%d)' % (peer.name(), peer.pwnd_run(), peer.pwnd_total()) if num_total > 1: if num_total > 9000: name += ' of over 9000' else: name += ' of %d' % num_total self.set('friend_face', peer.face()) self.set('friend_name', name) self.update() def on_new_peer(self, peer): face = '' # first time they met, neutral mood if peer.first_encounter(): face = random.choice((faces.AWAKE, faces.COOL)) # a good friend, positive expression elif peer.is_good_friend(self._config): face = random.choice((faces.MOTIVATED, faces.FRIEND, faces.HAPPY)) # normal friend, neutral-positive else: face = random.choice((faces.EXCITED, faces.HAPPY, faces.SMART)) self.set('face', face) self.set('status', self._voice.on_new_peer(peer)) self.update() time.sleep(3) def on_lost_peer(self, peer): self.set('face', faces.LONELY) self.set('status', self._voice.on_lost_peer(peer)) self.update() def on_free_channel(self, channel): self.set('face', faces.SMART) self.set('status', self._voice.on_free_channel(channel)) self.update() def wait(self, secs, sleeping=True): was_normal = self.is_normal() part = secs / 10.0 for step in range(0, 10): # if we weren't in a normal state before going # to sleep, keep that face and status on for # a while, otherwise the sleep animation will # always override any minor state change before it if was_normal or step > 5: if sleeping: if secs > 1: self.set('face', faces.SLEEP) self.set('status', self._voice.on_napping(int(secs))) else: self.set('face', faces.SLEEP2) self.set('status', self._voice.on_awakening()) else: self.set('status', self._voice.on_waiting(int(secs))) good_mood = self._agent.in_good_mood() if step % 2 == 0: self.set( 'face', faces.LOOK_R_HAPPY if good_mood else faces.LOOK_R) else: self.set( 'face', faces.LOOK_L_HAPPY if good_mood else faces.LOOK_L) time.sleep(part) secs -= part self.on_normal() def on_shutdown(self): self.set('face', faces.SLEEP) self.set('status', self._voice.on_shutdown()) self.update(force=True) self._frozen = True def on_bored(self): self.set('face', faces.BORED) self.set('status', self._voice.on_bored()) self.update() def on_sad(self): self.set('face', faces.SAD) self.set('status', self._voice.on_sad()) self.update() def on_motivated(self, reward): self.set('face', faces.MOTIVATED) self.set('status', self._voice.on_motivated(reward)) self.update() def on_demotivated(self, reward): self.set('face', faces.DEMOTIVATED) self.set('status', self._voice.on_demotivated(reward)) self.update() def on_excited(self): self.set('face', faces.EXCITED) self.set('status', self._voice.on_excited()) self.update() def on_assoc(self, ap): self.set('face', faces.INTENSE) self.set('status', self._voice.on_assoc(ap)) self.update() def on_deauth(self, sta): self.set('face', faces.COOL) self.set('status', self._voice.on_deauth(sta)) self.update() def on_miss(self, who): self.set('face', faces.SAD) self.set('status', self._voice.on_miss(who)) self.update() def on_grateful(self): self.set('face', faces.GRATEFUL) self.set('status', self._voice.on_grateful()) self.update() def on_lonely(self): self.set('face', faces.LONELY) self.set('status', self._voice.on_lonely()) self.update() def on_handshakes(self, new_shakes): self.set('face', faces.HAPPY) self.set('status', self._voice.on_handshakes(new_shakes)) self.update() def on_unread_messages(self, count, total): self.set('face', faces.EXCITED) self.set('status', self._voice.on_unread_messages(count, total)) self.update() time.sleep(5.0) def on_rebooting(self): self.set('face', faces.BROKEN) self.set('status', self._voice.on_rebooting()) self.update() def on_custom(self, text): self.set('face', faces.DEBUG) self.set('status', self._voice.custom(text)) self.update() def update(self, force=False, new_data={}): for key, val in new_data.items(): self.set(key, val) with self._lock: if self._frozen: return changes = self._state.changes(ignore=self._ignore_changes) if force or len(changes): self._canvas = Image.new('1', (self._width, self._height), WHITE) drawer = ImageDraw.Draw(self._canvas) plugins.on('ui_update', self) for key, lv in self._state.items(): lv.draw(self._canvas, drawer) for cb in self._render_cbs: cb(self._canvas) self._state.reset()
class LastSession(object): EPOCH_TOKEN = '[epoch ' EPOCH_PARSER = re.compile(r'^.+\[epoch (\d+)\] (.+)') EPOCH_DATA_PARSER = re.compile(r'([a-z_]+)=([^\s]+)') TRAINING_TOKEN = ' training epoch ' START_TOKEN = 'connecting to http' DEAUTH_TOKEN = 'deauthing ' ASSOC_TOKEN = 'sending association frame to ' HANDSHAKE_TOKEN = '!!! captured new handshake ' PEER_TOKEN = 'detected unit ' def __init__(self, config): self.config = config self.voice = Voice(lang=config['main']['lang']) self.path = config['main']['log'] self.last_session = [] self.last_session_id = '' self.last_saved_session_id = '' self.duration = '' self.duration_human = '' self.deauthed = 0 self.associated = 0 self.handshakes = 0 self.peers = 0 self.last_peer = None self.epochs = 0 self.train_epochs = 0 self.min_reward = 1000 self.max_reward = -1000 self.avg_reward = 0 self._peer_parser = re.compile( 'detected unit (.+)@(.+) \(v.+\) on channel \d+ \(([\d\-]+) dBm\) \[sid:(.+) pwnd_tot:(\d+) uptime:(\d+)\]') self.parsed = False def _get_last_saved_session_id(self): saved = '' try: with open(LAST_SESSION_FILE, 'rt') as fp: saved = fp.read().strip() except: saved = '' return saved def save_session_id(self): with open(LAST_SESSION_FILE, 'w+t') as fp: fp.write(self.last_session_id) self.last_saved_session_id = self.last_session_id def _parse_datetime(self, dt): dt = dt.split('.')[0] dt = dt.split(',')[0] dt = datetime.strptime(dt.split('.')[0], '%Y-%m-%d %H:%M:%S') return time.mktime(dt.timetuple()) def _parse_stats(self): self.duration = '' self.duration_human = '' self.deauthed = 0 self.associated = 0 self.handshakes = 0 self.epochs = 0 self.train_epochs = 0 self.peers = 0 self.last_peer = None self.min_reward = 1000 self.max_reward = -1000 self.avg_reward = 0 started_at = None stopped_at = None cache = {} for line in self.last_session: parts = line.split(']') if len(parts) < 2: continue try: line_timestamp = parts[0].strip('[') line = ']'.join(parts[1:]) stopped_at = self._parse_datetime(line_timestamp) if started_at is None: started_at = stopped_at if LastSession.DEAUTH_TOKEN in line and line not in cache: self.deauthed += 1 cache[line] = 1 elif LastSession.ASSOC_TOKEN in line and line not in cache: self.associated += 1 cache[line] = 1 elif LastSession.HANDSHAKE_TOKEN in line and line not in cache: self.handshakes += 1 cache[line] = 1 elif LastSession.TRAINING_TOKEN in line: self.train_epochs += 1 elif LastSession.EPOCH_TOKEN in line: self.epochs += 1 m = LastSession.EPOCH_PARSER.findall(line) if m: epoch_num, epoch_data = m[0] m = LastSession.EPOCH_DATA_PARSER.findall(epoch_data) for key, value in m: if key == 'reward': reward = float(value) self.avg_reward += reward if reward < self.min_reward: self.min_reward = reward elif reward > self.max_reward: self.max_reward = reward elif LastSession.PEER_TOKEN in line: m = self._peer_parser.findall(line) if m: name, pubkey, rssi, sid, pwnd_tot, uptime = m[0] if pubkey not in cache: self.last_peer = Peer({ 'session_id': sid, 'channel': 1, 'rssi': int(rssi), 'identity': pubkey, 'advertisement':{ 'name': name, 'pwnd_tot': int(pwnd_tot) }}) self.peers += 1 cache[pubkey] = self.last_peer else: cache[pubkey].adv['pwnd_tot'] = pwnd_tot except Exception as e: logging.error("error parsing line '%s': %s" % (line, e)) if started_at is not None: self.duration = stopped_at - started_at mins, secs = divmod(self.duration, 60) hours, mins = divmod(mins, 60) else: hours = mins = secs = 0 self.duration = '%02d:%02d:%02d' % (hours, mins, secs) self.duration_human = [] if hours > 0: self.duration_human.append('%d %s' % (hours, self.voice.hhmmss(hours, 'h'))) if mins > 0: self.duration_human.append('%d %s' % (mins, self.voice.hhmmss(mins, 'm'))) if secs > 0: self.duration_human.append('%d %s' % (secs, self.voice.hhmmss(secs, 's'))) self.duration_human = ', '.join(self.duration_human) self.avg_reward /= (self.epochs if self.epochs else 1) def parse(self): lines = [] if os.path.exists(self.path): with FileReadBackwards(self.path, encoding="utf-8") as fp: for line in fp: line = line.strip() if line != "" and line[0] != '[': continue lines.append(line) if LastSession.START_TOKEN in line: break lines.reverse() if len(lines) == 0: lines.append("Initial Session"); self.last_session = lines self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest() self.last_saved_session_id = self._get_last_saved_session_id() self._parse_stats() self.parsed = True def is_new(self): return self.last_session_id != self.last_saved_session_id
def __init__(self, config, state={}): self._render_cbs = [] self._config = config self._canvas = None self._lock = Lock() self._voice = Voice(lang=config['main']['lang']) self._width, self._height, \ face_pos, name_pos, status_pos = setup_display_specifics(config) self._state = State( state={ 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=(0, 0), label_font=fonts.Bold, text_font=fonts.Medium), 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=(30, 0), label_font=fonts.Bold, text_font=fonts.Medium), # 'epoch': LabeledValue(color=BLACK, label='E', value='0000', position=(145, 0), label_font=fonts.Bold, # text_font=fonts.Medium), 'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=(self._width - 65, 0), label_font=fonts.Bold, text_font=fonts.Medium), # 'square': Rect([1, 11, 124, 111]), 'line1': Line([ 0, int(self._height * .12), self._width, int(self._height * .12) ], color=BLACK), 'line2': Line([ 0, self._height - int(self._height * .12), self._width, self._height - int(self._height * .12) ], color=BLACK), # 'histogram': Histogram([4, 94], color = BLACK), 'face': Text(value=faces.SLEEP, position=face_pos, color=BLACK, font=fonts.Huge), 'friend_face': Text( value=None, position=(0, 90), font=fonts.Bold, color=BLACK), 'friend_name': Text(value=None, position=(40, 93), font=fonts.BoldSmall, color=BLACK), 'name': Text(value='%s>' % 'pwnagotchi', position=name_pos, color=BLACK, font=fonts.Bold), # 'face2': Bitmap( '/root/pwnagotchi/data/images/face_happy.bmp', (0, 20)), 'status': Text(value=self._voice.default(), position=status_pos, color=BLACK, font=fonts.Medium), 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, position=(0, self._height - int(self._height * .12) + 1), label_font=fonts.Bold, text_font=fonts.Medium), 'mode': Text(value='AUTO', position=(self._width - 25, self._height - int(self._height * .12) + 1), font=fonts.Bold, color=BLACK), }) for key, value in state.items(): self._state.set(key, value) _thread.start_new_thread(self._refresh_handler, ())
class LastSession: EPOCH_TOKEN = '[epoch ' EPOCH_PARSER = re.compile(r'^.+\[epoch (\d+)\] (.+)') EPOCH_DATA_PARSER = re.compile(r'([a-z_]+)=([^\s]+)') TRAINING_TOKEN = ' training epoch ' START_TOKEN = 'connecting to http' DEAUTH_TOKEN = 'deauthing ' ASSOC_TOKEN = 'sending association frame to ' HANDSHAKE_TOKEN = '!!! captured new handshake ' def __init__(self, config): self.config = config self.voice = Voice(lang=config['main']['lang']) self.path = config['main']['log']['path'] self.last_session = [] self.last_session_id = '' self.last_saved_session_id = '' self.duration = '' self.duration_human = '' self.deauthed = 0 self.associated = 0 self.handshakes = 0 self.epochs = 0 self.train_epochs = 0 self.min_reward = 1000 self.max_reward = -1000 self.avg_reward = 0 self.parsed = False def _get_last_saved_session_id(self): saved = '' try: with open(LAST_SESSION_FILE, 'rt') as fp: saved = fp.read().strip() except: saved = '' return saved def save_session_id(self): with open(LAST_SESSION_FILE, 'w+t') as fp: fp.write(self.last_session_id) self.last_saved_session_id = self.last_session_id def _parse_datetime(self, dt): dt = dt.split('.')[0] dt = dt.split(',')[0] dt = datetime.strptime(dt.split('.')[0], '%Y-%m-%d %H:%M:%S') return time.mktime(dt.timetuple()) def _parse_stats(self): self.duration = '' self.duration_human = '' self.deauthed = 0 self.associated = 0 self.handshakes = 0 self.epochs = 0 self.train_epochs = 0 self.min_reward = 1000 self.max_reward = -1000 self.avg_reward = 0 started_at = None stopped_at = None cache = {} for line in self.last_session: parts = line.split(']') if len(parts) < 2: continue try: line_timestamp = parts[0].strip('[') line = ']'.join(parts[1:]) stopped_at = self._parse_datetime(line_timestamp) if started_at is None: started_at = stopped_at if LastSession.DEAUTH_TOKEN in line and line not in cache: self.deauthed += 1 cache[line] = 1 elif LastSession.ASSOC_TOKEN in line and line not in cache: self.associated += 1 cache[line] = 1 elif LastSession.HANDSHAKE_TOKEN in line and line not in cache: self.handshakes += 1 cache[line] = 1 elif LastSession.TRAINING_TOKEN in line: self.train_epochs += 1 elif LastSession.EPOCH_TOKEN in line: self.epochs += 1 m = LastSession.EPOCH_PARSER.findall(line) if m: epoch_num, epoch_data = m[0] m = LastSession.EPOCH_DATA_PARSER.findall(epoch_data) for key, value in m: if key == 'reward': reward = float(value) self.avg_reward += reward if reward < self.min_reward: self.min_reward = reward elif reward > self.max_reward: self.max_reward = reward except Exception as e: logging.error("error parsing line '%s': %s" % (line, e)) if started_at is not None: self.duration = stopped_at - started_at mins, secs = divmod(self.duration, 60) hours, mins = divmod(mins, 60) else: hours = mins = secs = 0 self.duration = '%02d:%02d:%02d' % (hours, mins, secs) self.duration_human = [] if hours > 0: self.duration_human.append('%d %s' % (hours, self.voice.hhmmss(hours, 'h'))) if mins > 0: self.duration_human.append('%d %s' % (mins, self.voice.hhmmss(mins, 'm'))) if secs > 0: self.duration_human.append('%d %s' % (secs, self.voice.hhmmss(secs, 's'))) self.duration_human = ', '.join(self.duration_human) self.avg_reward /= (self.epochs if self.epochs else 1) def parse(self, ui, skip=False): if skip: logging.debug("skipping parsing of the last session logs ...") else: logging.debug("reading last session logs ...") ui.on_reading_logs() lines = [] if os.path.exists(self.path): with FileReadBackwards(self.path, encoding="utf-8") as fp: for line in fp: line = line.strip() if line != "" and line[0] != '[': continue lines.append(line) if LastSession.START_TOKEN in line: break lines_so_far = len(lines) if lines_so_far % 100 == 0: ui.on_reading_logs(lines_so_far) lines.reverse() if len(lines) == 0: lines.append("Initial Session") ui.on_reading_logs() self.last_session = lines self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest() self.last_saved_session_id = self._get_last_saved_session_id() logging.debug("parsing last session logs (%d lines) ..." % len(lines)) self._parse_stats() self.parsed = True def is_new(self): return self.last_session_id != self.last_saved_session_id