mirror of
https://github.com/AlienMajik/pwnagotchi_plugins.git
synced 2025-07-01 18:37:27 -04:00
Update age.py
New Enhancements in v2.0.3: Documented Training Logic: In on_epoch, a comment explains why train_epochs increments every 10 epochs: # Increment train_epochs every 10 epochs to simulate slower training progress. Enhanced File I/O Safety: In on_handshake, handshake logging is wrapped in a try-except block to handle file writing errors gracefully. Refined Decay Mechanics: In apply_decay, decay calculation uses floating-point division for smoother, more proportional point reduction. Increased Logging: Added debug and info logs for better transparency: on_epoch: Logs epoch number and points (logging.debug). check_achievements: Logs new titles (logging.info). apply_decay: Logs points lost due to decay (logging.info). on_handshake: Logs captured handshake details (logging.info). Thread Safety: Imported threading and added a data_lock in __init__. Used in save_data to ensure thread-safe file writing. Accurate Achievement Notifications: Tracks previous titles and stars to ensure new achievements are detected and announced correctly. Robust Handshake Handling: Adds type checking and error logging to prevent crashes from unexpected data, making the plugin more stable. Seamless New Installations: Fully initializes all attributes when starting fresh, improving reliability for new users. Persistent Progress: Saves achievement states to the data file, maintaining continuity across sessions.
This commit is contained in:
161
age.py
161
age.py
@ -3,6 +3,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
|
import threading
|
||||||
|
|
||||||
import pwnagotchi
|
import pwnagotchi
|
||||||
import pwnagotchi.plugins as plugins
|
import pwnagotchi.plugins as plugins
|
||||||
@ -13,7 +14,7 @@ from pwnagotchi.ui.view import BLACK
|
|||||||
|
|
||||||
class Age(plugins.Plugin):
|
class Age(plugins.Plugin):
|
||||||
__author__ = 'AlienMajik'
|
__author__ = 'AlienMajik'
|
||||||
__version__ = '2.0.2'
|
__version__ = '2.0.3'
|
||||||
__license__ = 'MIT'
|
__license__ = 'MIT'
|
||||||
__description__ = ('Enhanced plugin with achievement tiers, configurable titles, decay mechanics, '
|
__description__ = ('Enhanced plugin with achievement tiers, configurable titles, decay mechanics, '
|
||||||
'progress tracking, and dynamic status messages.')
|
'progress tracking, and dynamic status messages.')
|
||||||
@ -21,7 +22,7 @@ class Age(plugins.Plugin):
|
|||||||
DEFAULT_AGE_TITLES = {
|
DEFAULT_AGE_TITLES = {
|
||||||
1000: "Neon Spawn",
|
1000: "Neon Spawn",
|
||||||
2000: "Script Kiddie",
|
2000: "Script Kiddie",
|
||||||
5000: "WiFi Outlaw",
|
5000: "WiFi Outlaw",
|
||||||
10000: "Data Raider",
|
10000: "Data Raider",
|
||||||
25000: "Prophet",
|
25000: "Prophet",
|
||||||
33333: "Off the Grid"
|
33333: "Off the Grid"
|
||||||
@ -36,7 +37,7 @@ class Age(plugins.Plugin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# Default positions (x, y)
|
# Default UI positions (x, y)
|
||||||
self.default_positions = {
|
self.default_positions = {
|
||||||
'age': (10, 40),
|
'age': (10, 40),
|
||||||
'strength': (80, 40),
|
'strength': (80, 40),
|
||||||
@ -44,6 +45,7 @@ class Age(plugins.Plugin):
|
|||||||
'stars': (10, 80),
|
'stars': (10, 80),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Initialize core metrics
|
||||||
self.epochs = 0
|
self.epochs = 0
|
||||||
self.train_epochs = 0
|
self.train_epochs = 0
|
||||||
self.network_points = 0
|
self.network_points = 0
|
||||||
@ -60,20 +62,44 @@ class Age(plugins.Plugin):
|
|||||||
self.decay_amount = 10
|
self.decay_amount = 10
|
||||||
self.age_titles = self.DEFAULT_AGE_TITLES
|
self.age_titles = self.DEFAULT_AGE_TITLES
|
||||||
self.strength_titles = self.DEFAULT_STRENGTH_TITLES
|
self.strength_titles = self.DEFAULT_STRENGTH_TITLES
|
||||||
|
|
||||||
|
# Achievement tracking attributes
|
||||||
|
self.prev_age_title = "Unborn"
|
||||||
|
self.prev_strength_title = "Untrained"
|
||||||
|
self.prev_stars = 0
|
||||||
|
|
||||||
|
# Additional configurations
|
||||||
|
self.points_map = {
|
||||||
|
'wpa3': 10,
|
||||||
|
'wpa2': 5,
|
||||||
|
'wep': 2,
|
||||||
|
'wpa': 2
|
||||||
|
}
|
||||||
|
self.motivational_quotes = [
|
||||||
|
"Keep going, you're crushing it!",
|
||||||
|
"You're a WiFi wizard in the making!",
|
||||||
|
"More handshakes, more power!",
|
||||||
|
"Don't stop now, you're almost there!",
|
||||||
|
"Keep evolving, don't let decay catch you!"
|
||||||
|
]
|
||||||
|
self.data_lock = threading.Lock()
|
||||||
|
|
||||||
def on_loaded(self):
|
def on_loaded(self):
|
||||||
# Load configuration with fallbacks
|
# Load configuration options with fallbacks
|
||||||
self.max_stars = self.options.get('max_stars', 5)
|
self.max_stars = self.options.get('max_stars', 5)
|
||||||
self.star_interval = self.options.get('star_interval', 1000)
|
self.star_interval = self.options.get('star_interval', 1000)
|
||||||
self.decay_interval = self.options.get('decay_interval', 50)
|
self.decay_interval = self.options.get('decay_interval', 50)
|
||||||
self.decay_amount = self.options.get('decay_amount', 10)
|
self.decay_amount = self.options.get('decay_amount', 10)
|
||||||
self.age_titles = self.options.get('age_titles', self.DEFAULT_AGE_TITLES)
|
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.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.load_data()
|
self.load_data()
|
||||||
self.initialize_handshakes()
|
self.initialize_handshakes()
|
||||||
|
|
||||||
def initialize_handshakes(self):
|
def initialize_handshakes(self):
|
||||||
|
"""Initialize handshake count based on existing .pcap files."""
|
||||||
if self.handshake_count == 0 and os.path.isdir(self.handshake_dir):
|
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')]
|
existing = [f for f in os.listdir(self.handshake_dir) if f.endswith('.pcap')]
|
||||||
if existing:
|
if existing:
|
||||||
@ -82,6 +108,7 @@ class Age(plugins.Plugin):
|
|||||||
self.save_data()
|
self.save_data()
|
||||||
|
|
||||||
def get_age_title(self):
|
def get_age_title(self):
|
||||||
|
"""Determine age title based on epochs."""
|
||||||
thresholds = sorted(self.age_titles.keys(), reverse=True)
|
thresholds = sorted(self.age_titles.keys(), reverse=True)
|
||||||
for t in thresholds:
|
for t in thresholds:
|
||||||
if self.epochs >= t:
|
if self.epochs >= t:
|
||||||
@ -89,6 +116,7 @@ class Age(plugins.Plugin):
|
|||||||
return "Unborn"
|
return "Unborn"
|
||||||
|
|
||||||
def get_strength_title(self):
|
def get_strength_title(self):
|
||||||
|
"""Determine strength title based on train_epochs."""
|
||||||
thresholds = sorted(self.strength_titles.keys(), reverse=True)
|
thresholds = sorted(self.strength_titles.keys(), reverse=True)
|
||||||
for t in thresholds:
|
for t in thresholds:
|
||||||
if self.train_epochs >= t:
|
if self.train_epochs >= t:
|
||||||
@ -96,16 +124,11 @@ class Age(plugins.Plugin):
|
|||||||
return "Untrained"
|
return "Untrained"
|
||||||
|
|
||||||
def random_motivational_quote(self):
|
def random_motivational_quote(self):
|
||||||
quotes = [
|
"""Return a random motivational quote from the configurable list."""
|
||||||
"Keep going, you're crushing it!",
|
return random.choice(self.motivational_quotes)
|
||||||
"You're a WiFi wizard in the making!",
|
|
||||||
"More handshakes, more power!",
|
|
||||||
"Don't stop now, you're almost there!",
|
|
||||||
"Keep evolving, don't let decay catch you!"
|
|
||||||
]
|
|
||||||
return random.choice(quotes)
|
|
||||||
|
|
||||||
def random_inactivity_message(self, points_lost):
|
def random_inactivity_message(self, points_lost):
|
||||||
|
"""Return a random inactivity message with points lost."""
|
||||||
messages = [
|
messages = [
|
||||||
"Time to wake up, you're rusting!",
|
"Time to wake up, you're rusting!",
|
||||||
"Decayed by {points_lost}, keep it active!",
|
"Decayed by {points_lost}, keep it active!",
|
||||||
@ -116,33 +139,39 @@ class Age(plugins.Plugin):
|
|||||||
return random.choice(messages).format(points_lost=points_lost)
|
return random.choice(messages).format(points_lost=points_lost)
|
||||||
|
|
||||||
def check_achievements(self, agent):
|
def check_achievements(self, agent):
|
||||||
|
"""Check and announce new age or strength achievements."""
|
||||||
current_age = self.get_age_title()
|
current_age = self.get_age_title()
|
||||||
current_strength = self.get_strength_title()
|
current_strength = self.get_strength_title()
|
||||||
|
|
||||||
if current_age != self.prev_age_title:
|
if current_age != self.prev_age_title:
|
||||||
agent.view().set('face', faces.HAPPY)
|
agent.view().set('face', faces.HAPPY)
|
||||||
agent.view().set('status', f"🎉 {current_age} Achieved! {self.random_motivational_quote()}")
|
agent.view().set('status', f"🎉 {current_age} Achieved! {self.random_motivational_quote()}")
|
||||||
|
logging.info(f"[Age] New age title: {current_age}")
|
||||||
self.prev_age_title = current_age
|
self.prev_age_title = current_age
|
||||||
|
|
||||||
if current_strength != self.prev_strength_title:
|
if current_strength != self.prev_strength_title:
|
||||||
agent.view().set('face', faces.MOTIVATED)
|
agent.view().set('face', faces.MOTIVATED)
|
||||||
agent.view().set('status', f"💪 Evolved to {current_strength}!")
|
agent.view().set('status', f"💪 Evolved to {current_strength}!")
|
||||||
|
logging.info(f"[Age] New strength title: {current_strength}")
|
||||||
self.prev_strength_title = current_strength
|
self.prev_strength_title = current_strength
|
||||||
|
|
||||||
def apply_decay(self, agent):
|
def apply_decay(self, agent):
|
||||||
|
"""Apply decay to network points based on inactivity with refined mechanics."""
|
||||||
inactive_epochs = self.epochs - self.last_active_epoch
|
inactive_epochs = self.epochs - self.last_active_epoch
|
||||||
if inactive_epochs >= self.decay_interval:
|
if inactive_epochs >= self.decay_interval:
|
||||||
decay_cycles = inactive_epochs // self.decay_interval
|
decay_factor = inactive_epochs / self.decay_interval
|
||||||
points_lost = decay_cycles * self.decay_amount
|
points_lost = int(decay_factor * self.decay_amount)
|
||||||
self.network_points = max(0, self.network_points - points_lost)
|
self.network_points = max(0, self.network_points - points_lost)
|
||||||
|
|
||||||
if points_lost > 0:
|
if points_lost > 0:
|
||||||
agent.view().set('face', faces.SAD)
|
agent.view().set('face', faces.SAD)
|
||||||
agent.view().set('status', self.random_inactivity_message(points_lost))
|
agent.view().set('status', self.random_inactivity_message(points_lost))
|
||||||
|
logging.info(f"[Age] Applied decay: lost {points_lost} points due to inactivity")
|
||||||
self.last_active_epoch = self.epochs
|
self.last_active_epoch = self.epochs
|
||||||
self.save_data()
|
self.save_data()
|
||||||
|
|
||||||
def on_ui_setup(self, ui):
|
def on_ui_setup(self, ui):
|
||||||
|
"""Set up UI elements with configurable positions."""
|
||||||
def get_position(element):
|
def get_position(element):
|
||||||
x = self.options.get(
|
x = self.options.get(
|
||||||
f"{element}_x",
|
f"{element}_x",
|
||||||
@ -184,14 +213,18 @@ class Age(plugins.Plugin):
|
|||||||
position=positions['stars'], label_font=fonts.Bold, text_font=fonts.Medium))
|
position=positions['stars'], label_font=fonts.Bold, text_font=fonts.Medium))
|
||||||
|
|
||||||
def on_ui_update(self, ui):
|
def on_ui_update(self, ui):
|
||||||
|
"""Update UI elements with current values."""
|
||||||
ui.set('Age', self.get_age_title())
|
ui.set('Age', self.get_age_title())
|
||||||
ui.set('Strength', self.get_strength_title())
|
ui.set('Strength', self.get_strength_title())
|
||||||
ui.set('Points', self.abrev_number(self.network_points))
|
ui.set('Points', self.abrev_number(self.network_points))
|
||||||
ui.set('ReP', self.get_star_string())
|
ui.set('ReP', self.get_star_string())
|
||||||
|
|
||||||
def on_epoch(self, agent, epoch, epoch_data):
|
def on_epoch(self, agent, epoch, epoch_data):
|
||||||
|
"""Handle epoch events: increment counters, apply decay, and check achievements."""
|
||||||
self.epochs += 1
|
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
|
self.train_epochs += 1 if self.epochs % 10 == 0 else 0
|
||||||
|
logging.debug(f"[Age] Epoch {self.epochs}, Points: {self.network_points}")
|
||||||
|
|
||||||
self.apply_decay(agent)
|
self.apply_decay(agent)
|
||||||
self.check_achievements(agent)
|
self.check_achievements(agent)
|
||||||
@ -202,33 +235,49 @@ class Age(plugins.Plugin):
|
|||||||
self.save_data()
|
self.save_data()
|
||||||
|
|
||||||
def age_checkpoint(self, agent):
|
def age_checkpoint(self, agent):
|
||||||
# Status update at every epoch milestone (e.g., every 100 epochs)
|
"""Display milestone message every 100 epochs."""
|
||||||
view = agent.view()
|
view = agent.view()
|
||||||
view.set('face', faces.HAPPY)
|
view.set('face', faces.HAPPY)
|
||||||
view.set('status', f"Epoch milestone: {self.epochs} epochs!")
|
view.set('status', f"Epoch milestone: {self.epochs} epochs!")
|
||||||
view.update(force=True)
|
view.update(force=True)
|
||||||
|
|
||||||
def on_handshake(self, agent, *args):
|
def on_handshake(self, agent, *args):
|
||||||
self.last_active_epoch = self.epochs
|
"""Handle handshake events with enhanced error handling and logging."""
|
||||||
enc = args[2].get('encryption', '').lower()
|
try:
|
||||||
|
if len(args) < 3:
|
||||||
points = {
|
logging.warning("[Age] Insufficient arguments in on_handshake")
|
||||||
'wpa3': 10, 'wpa2': 5,
|
return
|
||||||
'wep': 2, 'wpa': 2
|
|
||||||
}.get(enc, 1)
|
ap = args[2]
|
||||||
|
if isinstance(ap, dict):
|
||||||
self.network_points += points
|
enc = ap.get('encryption', '').lower()
|
||||||
self.handshake_count += 1
|
essid = ap.get('essid', 'unknown')
|
||||||
|
else:
|
||||||
# Log details
|
logging.warning(f"[Age] AP is a string, not a dictionary: {ap}. Skipping handshake processing.")
|
||||||
with open(self.log_path, 'a') as f:
|
return
|
||||||
essid = args[2].get('essid', 'unknown')
|
|
||||||
f.write(f"{time.time()},{essid},{enc},{points}\n")
|
points = self.points_map.get(enc, 1)
|
||||||
|
|
||||||
self.new_star_checkpoint(agent)
|
self.network_points += points
|
||||||
self.save_data()
|
self.handshake_count += 1
|
||||||
|
self.last_active_epoch = self.epochs
|
||||||
|
|
||||||
|
# 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)}")
|
||||||
|
|
||||||
|
logging.info(f"[Age] Captured handshake: {essid}, encryption: {enc}, points gained: {points}")
|
||||||
|
|
||||||
|
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):
|
def new_star_checkpoint(self, agent):
|
||||||
|
"""Check and announce new star tier achievements."""
|
||||||
stars = self.get_stars_count()
|
stars = self.get_stars_count()
|
||||||
if stars > self.prev_stars:
|
if stars > self.prev_stars:
|
||||||
symbol = self.get_symbol_for_handshakes()
|
symbol = self.get_symbol_for_handshakes()
|
||||||
@ -237,32 +286,34 @@ class Age(plugins.Plugin):
|
|||||||
self.prev_stars = stars
|
self.prev_stars = stars
|
||||||
|
|
||||||
def load_data(self):
|
def load_data(self):
|
||||||
|
"""Load saved data from JSON file with defaults for new installations."""
|
||||||
try:
|
try:
|
||||||
if os.path.exists(self.data_path):
|
if os.path.exists(self.data_path):
|
||||||
with open(self.data_path, 'r') as f:
|
with open(self.data_path, 'r') as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
|
self.epochs = data.get('epochs', 0)
|
||||||
# Handle old format compatibility
|
self.train_epochs = data.get('train_epochs', 0)
|
||||||
self.epochs = data.get('epochs', data.get('epochs_lived', 0))
|
self.network_points = data.get('points', 0)
|
||||||
self.train_epochs = data.get('train_epochs', data.get('epochs_trained', 0))
|
self.handshake_count = data.get('handshakes', 0)
|
||||||
self.network_points = data.get('points', data.get('network_points', 0))
|
|
||||||
self.handshake_count = data.get('handshakes', data.get('handshake_count', 0))
|
|
||||||
|
|
||||||
# New fields with defaults
|
|
||||||
self.last_active_epoch = data.get('last_active', 0)
|
self.last_active_epoch = data.get('last_active', 0)
|
||||||
self.prev_age_title = data.get('prev_age', self.get_age_title())
|
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_strength_title = data.get('prev_strength', self.get_strength_title())
|
||||||
self.prev_stars = data.get('prev_stars', self.get_stars_count())
|
self.prev_stars = data.get('prev_stars', self.get_stars_count())
|
||||||
|
else:
|
||||||
# Migrate old format to new format
|
# Set defaults for a new installation
|
||||||
if 'epochs_lived' in data:
|
self.epochs = 0
|
||||||
self.save_data() # Resave in new format
|
self.train_epochs = 0
|
||||||
logging.info("[Age] Migrated old data format to new format")
|
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()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"[Age] Load error: {str(e)}")
|
logging.error(f"[Age] Load error: {str(e)}")
|
||||||
|
|
||||||
def save_data(self):
|
def save_data(self):
|
||||||
|
"""Save current data to JSON file with thread safety."""
|
||||||
data = {
|
data = {
|
||||||
'epochs': self.epochs,
|
'epochs': self.epochs,
|
||||||
'train_epochs': self.train_epochs,
|
'train_epochs': self.train_epochs,
|
||||||
@ -273,22 +324,27 @@ class Age(plugins.Plugin):
|
|||||||
'prev_strength': self.get_strength_title(),
|
'prev_strength': self.get_strength_title(),
|
||||||
'prev_stars': self.get_stars_count(),
|
'prev_stars': self.get_stars_count(),
|
||||||
}
|
}
|
||||||
try:
|
with self.data_lock:
|
||||||
with open(self.data_path, 'w') as f:
|
try:
|
||||||
json.dump(data, f, indent=2)
|
with open(self.data_path, 'w') as f:
|
||||||
except Exception as e:
|
json.dump(data, f, indent=2)
|
||||||
logging.error(f"[Age] Save error: {str(e)}")
|
except Exception as e:
|
||||||
|
logging.error(f"[Age] Save error: {str(e)}")
|
||||||
|
|
||||||
def get_stars_count(self):
|
def get_stars_count(self):
|
||||||
|
"""Calculate current number of stars."""
|
||||||
return min(self.handshake_count // self.star_interval, self.max_stars)
|
return min(self.handshake_count // self.star_interval, self.max_stars)
|
||||||
|
|
||||||
def get_symbol_for_handshakes(self):
|
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 '★'
|
return '♣' if self.handshake_count >= 10000 else '♦' if self.handshake_count >= 5000 else '★'
|
||||||
|
|
||||||
def get_star_string(self):
|
def get_star_string(self):
|
||||||
|
"""Return string of star symbols."""
|
||||||
return self.get_symbol_for_handshakes() * self.get_stars_count()
|
return self.get_symbol_for_handshakes() * self.get_stars_count()
|
||||||
|
|
||||||
def abrev_number(self, num):
|
def abrev_number(self, num):
|
||||||
|
"""Abbreviate large numbers (e.g., 1000 -> 1K)."""
|
||||||
for unit in ['','K','M','B']:
|
for unit in ['','K','M','B']:
|
||||||
if abs(num) < 1000:
|
if abs(num) < 1000:
|
||||||
return f"{num:.1f}{unit}".rstrip('.0')
|
return f"{num:.1f}{unit}".rstrip('.0')
|
||||||
@ -298,3 +354,4 @@ class Age(plugins.Plugin):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user