diff --git a/age.py b/age.py index 354d549..cc76087 100644 --- a/age.py +++ b/age.py @@ -14,26 +14,35 @@ from pwnagotchi.ui.view import BLACK class Age(plugins.Plugin): __author__ = 'AlienMajik' - __version__ = '2.0.3' + __version__ = '3.1.0' __license__ = 'MIT' - __description__ = ('Enhanced plugin with achievement tiers, configurable titles, decay mechanics, ' - 'progress tracking, and dynamic status messages.') + __description__ = ('An enhanced plugin with frequent titles, dynamic quotes, progress bars, ' + 'random events, handshake streaks, personality evolution, and secret achievements. ' + 'UI is optimized to avoid clutter.') DEFAULT_AGE_TITLES = { + 100: "Baby Steps", + 500: "Getting the Hang of It", 1000: "Neon Spawn", 2000: "Script Kiddie", 5000: "WiFi Outlaw", 10000: "Data Raider", 25000: "Prophet", - 33333: "Off the Grid" + 33333: "Off the Grid", + 55555: "Multiversed", + 111111: "Intergalactic" } DEFAULT_STRENGTH_TITLES = { + 100: "Sparring Novice", + 300: "Gear Tickler", 500: "Fleshbag", 1500: "Lightweight", 2000: "Deauth King", 2500: "Handshake Hunter", - 3333: "Unstoppable" + 3333: "Unstoppable", + 55555: "Rev-9", + 111111: "Kuato" } def __init__(self): @@ -42,10 +51,11 @@ class Age(plugins.Plugin): 'age': (10, 40), 'strength': (80, 40), 'points': (10, 60), - 'stars': (10, 80), + 'progress': (10, 80), + 'personality': (10, 100), } - # Initialize core metrics + # Core metrics self.epochs = 0 self.train_epochs = 0 self.network_points = 0 @@ -55,20 +65,18 @@ class Age(plugins.Plugin): self.log_path = '/root/network_points.log' self.handshake_dir = '/home/pi/handshakes' - # Configurable settings with defaults - self.max_stars = 5 - self.star_interval = 1000 + # Configurable settings self.decay_interval = 50 self.decay_amount = 10 self.age_titles = self.DEFAULT_AGE_TITLES self.strength_titles = self.DEFAULT_STRENGTH_TITLES + self.show_personality = False # Default to False to avoid clutter - # Achievement tracking attributes + # Achievement tracking self.prev_age_title = "Unborn" self.prev_strength_title = "Untrained" - self.prev_stars = 0 - # Additional configurations + # Points and quotes self.points_map = { 'wpa3': 10, 'wpa2': 5, @@ -82,18 +90,29 @@ class Age(plugins.Plugin): "Don't stop now, you're almost there!", "Keep evolving, don't let decay catch you!" ] + + # New features + self.last_handshake_enc = None + self.last_decay_points = 0 + self.streak = 0 + self.active_event = None + self.event_handshakes_left = 0 + self.event_multiplier = 1.0 + self.personality_points = {'aggro': 0, 'stealth': 0, 'scholar': 0} + self.night_owl_handshakes = 0 + self.enc_types_captured = set() + self.data_lock = threading.Lock() def on_loaded(self): # Load configuration options with fallbacks - self.max_stars = self.options.get('max_stars', 5) - self.star_interval = self.options.get('star_interval', 1000) self.decay_interval = self.options.get('decay_interval', 50) self.decay_amount = self.options.get('decay_amount', 10) self.age_titles = self.options.get('age_titles', self.DEFAULT_AGE_TITLES) self.strength_titles = self.options.get('strength_titles', self.DEFAULT_STRENGTH_TITLES) self.points_map = self.options.get('points_map', self.points_map) self.motivational_quotes = self.options.get('motivational_quotes', self.motivational_quotes) + self.show_personality = self.options.get('show_personality', False) self.load_data() self.initialize_handshakes() @@ -102,10 +121,9 @@ class Age(plugins.Plugin): """Initialize handshake count based on existing .pcap files.""" if self.handshake_count == 0 and os.path.isdir(self.handshake_dir): existing = [f for f in os.listdir(self.handshake_dir) if f.endswith('.pcap')] - if existing: - self.handshake_count = len(existing) - logging.info(f"[Age] Initialized with {self.handshake_count} handshakes") - self.save_data() + self.handshake_count = len(existing) + logging.info(f"[Age] Initialized with {self.handshake_count} handshakes") + self.save_data() def get_age_title(self): """Determine age title based on epochs.""" @@ -124,19 +142,28 @@ class Age(plugins.Plugin): return "Untrained" def random_motivational_quote(self): - """Return a random motivational quote from the configurable list.""" - return random.choice(self.motivational_quotes) + """Return a context-aware motivational quote.""" + if self.last_handshake_enc: + quote = f"Boom! That {self.last_handshake_enc.upper()} never saw you coming." + self.last_handshake_enc = None + return quote + elif self.last_decay_points > 0: + quote = f"Decay stung for {self.last_decay_points}. Time to fight back!" + self.last_decay_points = 0 + return quote + else: + return random.choice(self.motivational_quotes) def random_inactivity_message(self, points_lost): """Return a random inactivity message with points lost.""" messages = [ - "Time to wake up, you're rusting!", - "Decayed by {points_lost}, keep it active!", + f"Time to wake up, lost {points_lost} to rust!", + f"Decayed by {points_lost}, keep it active!", "Stale, but you can still revive!", "Don't let inactivity hold you back!", "Keep moving, no room for decay!" ] - return random.choice(messages).format(points_lost=points_lost) + return random.choice(messages) def check_achievements(self, agent): """Check and announce new age or strength achievements.""" @@ -156,7 +183,7 @@ class Age(plugins.Plugin): self.prev_strength_title = current_strength def apply_decay(self, agent): - """Apply decay to network points based on inactivity with refined mechanics.""" + """Apply decay to network points based on inactivity.""" inactive_epochs = self.epochs - self.last_active_epoch if inactive_epochs >= self.decay_interval: decay_factor = inactive_epochs / self.decay_interval @@ -164,37 +191,22 @@ class Age(plugins.Plugin): self.network_points = max(0, self.network_points - points_lost) if points_lost > 0: + self.last_decay_points = points_lost + self.streak = 0 # Reset streak on decay agent.view().set('face', faces.SAD) agent.view().set('status', self.random_inactivity_message(points_lost)) - logging.info(f"[Age] Applied decay: lost {points_lost} points due to inactivity") + logging.info(f"[Age] Applied decay: lost {points_lost} points") self.last_active_epoch = self.epochs self.save_data() def on_ui_setup(self, ui): """Set up UI elements with configurable positions.""" def get_position(element): - x = self.options.get( - f"{element}_x", - self.options.get( - f"{element}_x_coord", # Backwards compatibility - self.default_positions[element][0] - ) - ) - y = self.options.get( - f"{element}_y", - self.options.get( - f"{element}_y_coord", # Backwards compatibility - self.default_positions[element][1] - ) - ) + x = self.options.get(f"{element}_x", self.default_positions[element][0]) + y = self.options.get(f"{element}_y", self.default_positions[element][1]) return (int(x), int(y)) - positions = { - 'age': get_position('age'), - 'strength': get_position('strength'), - 'points': get_position('points'), - 'stars': get_position('stars'), - } + positions = {key: get_position(key) for key in self.default_positions if key != 'stars'} ui.add_element('Age', LabeledValue( color=BLACK, label='Age', value="Newborn", @@ -205,35 +217,77 @@ class Age(plugins.Plugin): position=positions['strength'], label_font=fonts.Bold, text_font=fonts.Medium)) ui.add_element('Points', LabeledValue( - color=BLACK, label='★ Pts', value="0", + color=BLACK, label='Pts', value="0", position=positions['points'], label_font=fonts.Bold, text_font=fonts.Medium)) - ui.add_element('ReP', LabeledValue( - color=BLACK, label='ReP', value="★", - position=positions['stars'], label_font=fonts.Bold, text_font=fonts.Medium)) + ui.add_element('Progress', LabeledValue( + color=BLACK, label='Next Age', value="[ ]", + position=positions['progress'], label_font=fonts.Bold, text_font=fonts.Medium)) + + if self.show_personality: + ui.add_element('Personality', LabeledValue( + color=BLACK, label='Trait', value="Neutral", + position=positions['personality'], label_font=fonts.Bold, text_font=fonts.Medium)) def on_ui_update(self, ui): """Update UI elements with current values.""" ui.set('Age', self.get_age_title()) ui.set('Strength', self.get_strength_title()) ui.set('Points', self.abrev_number(self.network_points)) - ui.set('ReP', self.get_star_string()) + + # Update progress bar for next age title + next_threshold = self.get_next_age_threshold() + if next_threshold: + progress = self.epochs / next_threshold + bar_length = 5 + filled = int(progress * bar_length) + bar = '[' + '=' * filled + ' ' * (bar_length - filled) + ']' + ui.set('Progress', bar) + else: + ui.set('Progress', '[MAX]') + + if self.show_personality: + ui.set('Personality', self.get_dominant_personality()) + + def get_next_age_threshold(self): + """Get the next age title threshold.""" + thresholds = sorted(self.age_titles.keys()) + for t in thresholds: + if self.epochs < t: + return t + return None # Max level reached def on_epoch(self, agent, epoch, epoch_data): - """Handle epoch events: increment counters, apply decay, and check achievements.""" + """Handle epoch events.""" self.epochs += 1 - # Increment train_epochs every 10 epochs to simulate slower training progress self.train_epochs += 1 if self.epochs % 10 == 0 else 0 + if self.epochs % 10 == 0: + self.personality_points['scholar'] += 1 + logging.debug(f"[Age] Epoch {self.epochs}, Points: {self.network_points}") self.apply_decay(agent) self.check_achievements(agent) if self.epochs % 100 == 0: + self.handle_random_event(agent) self.age_checkpoint(agent) self.save_data() + def handle_random_event(self, agent): + """Trigger a random event with 5% chance every 100 epochs.""" + if random.random() < 0.05: + events = [ + {"description": "Lucky Break: Double points for next 5 handshakes!", "multiplier": 2.0, "handshakes": 5}, + {"description": "Signal Noise: Next handshake worth half points.", "multiplier": 0.5, "handshakes": 1}, + ] + self.active_event = random.choice(events) + self.event_handshakes_left = self.active_event["handshakes"] + self.event_multiplier = self.active_event["multiplier"] + agent.view().set('status', self.active_event["description"]) + logging.info(f"[Age] Random event: {self.active_event['description']}") + def age_checkpoint(self, agent): """Display milestone message every 100 epochs.""" view = agent.view() @@ -242,7 +296,7 @@ class Age(plugins.Plugin): view.update(force=True) def on_handshake(self, agent, *args): - """Handle handshake events with enhanced error handling and logging.""" + """Handle handshake events with streaks and secret achievements.""" try: if len(args) < 3: logging.warning("[Age] Insufficient arguments in on_handshake") @@ -253,40 +307,60 @@ class Age(plugins.Plugin): enc = ap.get('encryption', '').lower() essid = ap.get('essid', 'unknown') else: - logging.warning(f"[Age] AP is a string, not a dictionary: {ap}. Skipping handshake processing.") + logging.warning(f"[Age] AP is a string: {ap}") return + # Base points points = self.points_map.get(enc, 1) + # Apply streak bonus + self.streak += 1 + streak_threshold = 5 + streak_bonus = 1.2 + if self.streak >= streak_threshold: + points *= streak_bonus + agent.view().set('status', f"Streak bonus! +{int((streak_bonus - 1) * 100)}% points") + + # Apply random event multiplier + if self.active_event and self.event_handshakes_left > 0: + points *= self.event_multiplier + self.event_handshakes_left -= 1 + if self.event_handshakes_left == 0: + self.active_event = None + self.event_multiplier = 1.0 + + points = int(points) self.network_points += points self.handshake_count += 1 self.last_active_epoch = self.epochs + self.last_handshake_enc = enc + self.personality_points['aggro'] += 1 - # Log handshake details with enhanced file I/O safety - try: - with open(self.log_path, 'a') as f: - f.write(f"{time.time()},{essid},{enc},{points}\n") - except Exception as e: - logging.error(f"[Age] Failed to log handshake: {str(e)}") + # Secret achievements + current_hour = time.localtime().tm_hour + if 2 <= current_hour < 4: + self.night_owl_handshakes += 1 + if self.night_owl_handshakes == 10: + agent.view().set('status', "Achievement Unlocked: Night Owl!") + self.network_points += 50 # Bonus - logging.info(f"[Age] Captured handshake: {essid}, encryption: {enc}, points gained: {points}") + self.enc_types_captured.add(enc) + if self.enc_types_captured == set(self.points_map.keys()): + agent.view().set('status', "Achievement Unlocked: Crypto King!") + self.network_points += 100 # Bonus + + # Log handshake + with open(self.log_path, 'a') as f: + f.write(f"{time.time()},{essid},{enc},{points}\n") + + logging.info(f"[Age] Handshake: {essid}, enc: {enc}, points: {points}, streak: {self.streak}") - self.new_star_checkpoint(agent) self.save_data() except Exception as e: - logging.error(f"[Age] Error in handshake processing: {str(e)}") - - def new_star_checkpoint(self, agent): - """Check and announce new star tier achievements.""" - stars = self.get_stars_count() - if stars > self.prev_stars: - symbol = self.get_symbol_for_handshakes() - agent.view().set('face', faces.EXCITED) - agent.view().set('status', f"New {symbol} Tier Achieved!") - self.prev_stars = stars + logging.error(f"[Age] Handshake error: {str(e)}") def load_data(self): - """Load saved data from JSON file with defaults for new installations.""" + """Load saved data from JSON file.""" try: if os.path.exists(self.data_path): with open(self.data_path, 'r') as f: @@ -298,17 +372,11 @@ class Age(plugins.Plugin): self.last_active_epoch = data.get('last_active', 0) self.prev_age_title = data.get('prev_age', self.get_age_title()) self.prev_strength_title = data.get('prev_strength', self.get_strength_title()) - self.prev_stars = data.get('prev_stars', self.get_stars_count()) - else: - # Set defaults for a new installation - self.epochs = 0 - self.train_epochs = 0 - self.network_points = 0 - self.handshake_count = 0 - self.last_active_epoch = 0 - self.prev_age_title = self.get_age_title() - self.prev_strength_title = self.get_strength_title() - self.prev_stars = self.get_stars_count() + self.streak = data.get('streak', 0) + self.night_owl_handshakes = data.get('night_owl_handshakes', 0) + self.enc_types_captured = set(data.get('enc_types_captured', [])) + for trait in ['aggro', 'stealth', 'scholar']: + self.personality_points[trait] = data.get(f'personality_{trait}', 0) except Exception as e: logging.error(f"[Age] Load error: {str(e)}") @@ -322,7 +390,12 @@ class Age(plugins.Plugin): 'last_active': self.last_active_epoch, 'prev_age': self.get_age_title(), 'prev_strength': self.get_strength_title(), - 'prev_stars': self.get_stars_count(), + 'streak': self.streak, + 'night_owl_handshakes': self.night_owl_handshakes, + 'enc_types_captured': list(self.enc_types_captured), + 'personality_aggro': self.personality_points['aggro'], + 'personality_stealth': self.personality_points['stealth'], + 'personality_scholar': self.personality_points['scholar'], } with self.data_lock: try: @@ -331,27 +404,17 @@ class Age(plugins.Plugin): except Exception as e: logging.error(f"[Age] Save error: {str(e)}") - def get_stars_count(self): - """Calculate current number of stars.""" - return min(self.handshake_count // self.star_interval, self.max_stars) - - def get_symbol_for_handshakes(self): - """Return symbol based on handshake count.""" - return '♣' if self.handshake_count >= 10000 else '♦' if self.handshake_count >= 5000 else '★' - - def get_star_string(self): - """Return string of star symbols.""" - return self.get_symbol_for_handshakes() * self.get_stars_count() + def get_dominant_personality(self): + """Determine dominant personality trait.""" + if not any(self.personality_points.values()): + return "Neutral" + dominant = max(self.personality_points, key=self.personality_points.get) + return dominant.capitalize() def abrev_number(self, num): - """Abbreviate large numbers (e.g., 1000 -> 1K).""" + """Abbreviate large numbers.""" for unit in ['','K','M','B']: if abs(num) < 1000: return f"{num:.1f}{unit}".rstrip('.0') num /= 1000.0 return f"{num:.1f}T" - - - - -