#!/usr/bin/env python3 # -*- coding: utf-8 -*- # IP Address Information Tool for Stormux # Provides local and remote IP addresses with speech-friendly formatting import sys import subprocess import re import socket import urllib.request import urllib.error import speechd def init_speech(): """Initialize speech client""" try: client = speechd.SSIPClient('ip_info') client.set_priority(speechd.Priority.IMPORTANT) client.set_punctuation(speechd.PunctuationMode.SOME) return client except Exception as e: print(f"Could not initialize speech: {e}") return None def speak(client, text): """Speak text using speech client""" if client: try: client.speak(text) except Exception: print(text) else: print(text) def format_ip_for_speech(ip_address): """Format IP address for clear speech synthesis""" if not ip_address: return "No IP address found" # Split IP into parts and replace dots with "dot" parts = ip_address.split('.') if len(parts) != 4: return ip_address # Return as-is if not a standard IPv4 # Join with " dot " and add spaces between digits for clarity formatted_parts = [] for part in parts: # Add spaces between digits for better speech clarity spaced_digits = ' '.join(part) formatted_parts.append(spaced_digits) return ' dot '.join(formatted_parts) def get_local_ip(): """Get the local IP address using multiple methods""" # Method 1: Try to connect to a remote host to determine local IP try: # Create a socket and connect to a remote address # This doesn't actually send data, just determines routing sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.connect(("8.8.8.8", 80)) local_ip = sock.getsockname()[0] sock.close() # Validate it's not a loopback address if not local_ip.startswith('127.'): return local_ip except Exception: pass # Method 2: Parse ip route command output try: result = subprocess.run(['ip', 'route', 'get', '8.8.8.8'], capture_output=True, text=True, check=True) # Look for "src" in the output match = re.search(r'src\s+(\d+\.\d+\.\d+\.\d+)', result.stdout) if match: return match.group(1) except Exception: pass # Method 3: Parse ip addr show output for common network interfaces try: result = subprocess.run(['ip', 'addr', 'show'], capture_output=True, text=True, check=True) # Look for inet addresses that are not loopback # Common patterns: 192.168.x.x, 10.x.x.x, 172.16-31.x.x pattern = r'inet\s+(\d+\.\d+\.\d+\.\d+)/\d+' matches = re.findall(pattern, result.stdout) for ip in matches: # Skip loopback if ip.startswith('127.'): continue # Prefer common private network ranges if (ip.startswith('192.168.') or ip.startswith('10.') or re.match(r'^172\.(1[6-9]|2[0-9]|3[01])\.', ip)): return ip # If no private IPs found, return the first non-loopback for ip in matches: if not ip.startswith('127.'): return ip except Exception: pass # Method 4: Fallback using hostname try: hostname = socket.gethostname() return socket.gethostbyname(hostname) except Exception: pass return None def get_remote_ip(): """Get the remote/public IP address""" services = [ 'https://icanhazip.com', 'https://ipecho.net/plain', 'https://ifconfig.me/ip', 'https://api.ipify.org' ] for service in services: try: with urllib.request.urlopen(service, timeout=10) as response: ip = response.read().decode('utf-8').strip() # Validate it looks like an IP address if re.match(r'^\d+\.\d+\.\d+\.\d+$', ip): return ip except Exception: continue return None def main(): if len(sys.argv) != 2 or sys.argv[1] not in ['local', 'remote']: print("Usage: ip_info.py [local|remote]") sys.exit(1) mode = sys.argv[1] speech_client = init_speech() if mode == 'local': print("Getting local IP address...") ip = get_local_ip() if ip: formatted_ip = format_ip_for_speech(ip) message = f"Local IP address: {formatted_ip}" speak(speech_client, message) print(f"Local IP: {ip}") else: message = "Could not determine local IP address" speak(speech_client, message) print(message) elif mode == 'remote': print("Getting remote IP address...") ip = get_remote_ip() if ip: formatted_ip = format_ip_for_speech(ip) message = f"Remote IP address: {formatted_ip}" speak(speech_client, message) print(f"Remote IP: {ip}") else: message = "Could not determine remote IP address. Check internet connection." speak(speech_client, message) print(message) # Clean up speech client if speech_client: try: speech_client.close() except: pass if __name__ == "__main__": main()