import requests from urllib.parse import quote from functools import lru_cache import os import json from functools import lru_cache apple_device_mapping = { "Watch1,1": "Apple Watch 38mm case", "Watch1,2": "Apple Watch 42mm case", "Watch2,3": "Apple Watch Series 2 38mm case", "Watch2,4": "Apple Watch Series 2 42mm case", "Watch2,6": "Apple Watch Series 1 38mm case", "Watch2,7": "Apple Watch Series 1 42mm case", "Watch3,1": "Apple Watch Series 3 38mm case", "Watch3,2": "Apple Watch Series 3 42mm case", "Watch3,3": "Apple Watch Series 3 38mm case", "Watch3,4": "Apple Watch Series 3 42mm case", "Watch4,1": "Apple Watch Series 4 40mm case", "Watch4,2": "Apple Watch Series 4 44mm case", "Watch4,3": "Apple Watch Series 4 40mm case", "Watch4,4": "Apple Watch Series 4 44mm case", "Watch5,1": "Apple Watch Series 5 40mm case", "Watch5,10": "Apple Watch SE 44mm case", "Watch5,11": "Apple Watch SE 40mm case", "Watch5,12": "Apple Watch SE 44mm case", "Watch5,2": "Apple Watch Series 5 44mm case", "Watch5,3": "Apple Watch Series 5 40mm case", "Watch5,4": "Apple Watch Series 5 44mm case", "Watch5,9": "Apple Watch SE 40mm case", "Watch6,1": "Apple Watch Series 6 40mm case", "Watch6,10": "Apple Watch SE 40mm case", "Watch6,11": "Apple Watch SE 44mm case", "Watch6,12": "Apple Watch SE 40mm case", "Watch6,13": "Apple Watch SE 44mm case", "Watch6,14": "Apple Watch Series 8 41mm case", "Watch6,15": "Apple Watch Series 8 45mm case", "Watch6,16": "Apple Watch Series 8 41mm case", "Watch6,17": "Apple Watch Series 8 45mm case", "Watch6,18": "Apple Watch Ultra", "Watch6,2": "Apple Watch Series 6 44mm case", "Watch6,3": "Apple Watch Series 6 40mm case", "Watch6,4": "Apple Watch Series 6 44mm case", "Watch6,6": "Apple Watch Series 7 41mm case", "Watch6,7": "Apple Watch Series 7 45mm case", "Watch6,8": "Apple Watch Series 7 41mm case", "Watch6,9": "Apple Watch Series 7 45mm case", "Watch7,1": "Apple Watch Series 9 41mm case", "Watch7,10": "Apple Watch Series 10 42mm case", "Watch7,11": "Apple Watch Series 10 46mm", "Watch7,2": "Apple Watch Series 9 45mm case", "Watch7,3": "Apple Watch Series 9 41mm case", "Watch7,4": "Apple Watch Series 9 45mm case", "Watch7,5": "Apple Watch Ultra 2", "Watch7,8": "Apple Watch Series 10 42mm case", "Watch7,9": "Apple Watch Series 10 46mm case (GPS", "arm64": "iPhone Simulator", "i386": "iPhone Simulator", "iPad1,1": "iPad", "iPad1,2": "iPad 3G", "iPad11,1": "iPad mini 5th Gen", "iPad11,2": "iPad mini 5th Gen", "iPad11,3": "iPad Air 3rd Gen", "iPad11,4": "iPad Air 3rd Gen", "iPad11,6": "iPad 8th Gen", "iPad11,7": "iPad 8th Gen", "iPad12,1": "iPad 9th Gen", "iPad12,2": "iPad 9th Gen", "iPad13,1": "iPad Air 4th Gen", "iPad13,10": "iPad Pro 12.9 inch 5th Gen", "iPad13,11": "iPad Pro 12.9 inch 5th Gen", "iPad13,16": "iPad Air 5th Gen", "iPad13,17": "iPad Air 5th Gen", "iPad13,18": "iPad 10th Gen", "iPad13,19": "iPad 10th Gen", "iPad13,2": "iPad Air 4th Gen", "iPad13,4": "iPad Pro 11 inch 5th Gen", "iPad13,5": "iPad Pro 11 inch 5th Gen", "iPad13,6": "iPad Pro 11 inch 5th Gen", "iPad13,7": "iPad Pro 11 inch 5th Gen", "iPad13,8": "iPad Pro 12.9 inch 5th Gen", "iPad13,9": "iPad Pro 12.9 inch 5th Gen", "iPad14,1": "iPad mini 6th Gen", "iPad14,10": "iPad Air 13 inch 6th Gen", "iPad14,11": "iPad Air 13 inch 6th Gen", "iPad14,2": "iPad mini 6th Gen", "iPad14,3": "iPad Pro 11 inch 4th Gen", "iPad14,4": "iPad Pro 11 inch 4th Gen", "iPad14,5": "iPad Pro 12.9 inch 6th Gen", "iPad14,6": "iPad Pro 12.9 inch 6th Gen", "iPad14,8": "iPad Air 11 inch 6th Gen", "iPad14,9": "iPad Air 11 inch 6th Gen", "iPad15,3": "iPad Air 11-inch 7th Gen", "iPad15,4": "iPad Air 11-inch 7th Gen", "iPad15,5": "iPad Air 13-inch 7th Gen", "iPad15,6": "iPad Air 13-inch 7th Gen", "iPad15,7": "iPad 11th Gen", "iPad15,8": "iPad 11th Gen", "iPad16,1": "iPad mini 7th Gen", "iPad16,2": "iPad mini 7th Gen", "iPad16,3": "iPad Pro 11 inch 5th Gen", "iPad16,4": "iPad Pro 11 inch 5th Gen", "iPad16,5": "iPad Pro 12.9 inch 7th Gen", "iPad16,6": "iPad Pro 12.9 inch 7th Gen", "iPad2,1": "2nd Gen iPad", "iPad2,2": "2nd Gen iPad", "iPad2,3": "2nd Gen iPad", "iPad2,4": "2nd Gen iPad New Revision", "iPad2,5": "iPad mini", "iPad2,6": "iPad mini", "iPad2,7": "iPad mini", "iPad3,1": "3rd Gen iPad", "iPad3,2": "3rd Gen iPad", "iPad3,3": "3rd Gen iPad", "iPad3,4": "4th Gen iPad", "iPad3,5": "4th Gen iPad", "iPad3,6": "4th Gen iPad", "iPad4,1": "iPad Air", "iPad4,2": "iPad Air", "iPad4,3": "1st Gen iPad Air", "iPad4,4": "iPad mini Retina", "iPad4,5": "iPad mini Retina", "iPad4,6": "iPad mini Retina", "iPad4,7": "iPad mini 3", "iPad4,8": "iPad mini 3", "iPad4,9": "iPad Mini 3", "iPad5,1": "iPad mini 4", "iPad5,2": "4th Gen iPad mini", "iPad5,3": "iPad Air 2", "iPad5,4": "iPad Air 2", "iPad6,11": "iPad (2017)", "iPad6,12": "iPad (2017)", "iPad6,3": "iPad Pro (9.7 inch)", "iPad6,4": "iPad Pro (9.7 inch)", "iPad6,7": "iPad Pro (12.9 inch)", "iPad6,8": "iPad Pro (12.9 inch)", "iPad7,1": "iPad Pro 2nd Gen", "iPad7,11": "iPad 7th Gen 10.2-inch", "iPad7,12": "iPad 7th Gen 10.2-inch", "iPad7,2": "iPad Pro 2nd Gen", "iPad7,3": "iPad Pro 10.5-inch 2nd Gen", "iPad7,4": "iPad Pro 10.5-inch 2nd Gen", "iPad7,5": "iPad 6th Gen", "iPad7,6": "iPad 6th Gen", "iPad8,1": "iPad Pro 11 inch 3rd Gen", "iPad8,10": "iPad Pro 11 inch 4th Gen", "iPad8,11": "iPad Pro 12.9 inch 4th Gen", "iPad8,12": "iPad Pro 12.9 inch 4th Gen", "iPad8,2": "iPad Pro 11 inch 3rd Gen", "iPad8,3": "iPad Pro 11 inch 3rd Gen", "iPad8,4": "iPad Pro 11 inch 3rd Gen", "iPad8,5": "iPad Pro 12.9 inch 3rd Gen", "iPad8,6": "iPad Pro 12.9 inch 3rd Gen", "iPad8,7": "iPad Pro 12.9 inch 3rd Gen", "iPad8,8": "iPad Pro 12.9 inch 3rd Gen", "iPad8,9": "iPad Pro 11 inch 4th Gen", "iPhone1,1": "iPhone", "iPhone1,2": "iPhone 3G", "iPhone10,1": "iPhone 8", "iPhone10,2": "iPhone 8 Plus", "iPhone10,3": "iPhone X", "iPhone10,4": "iPhone 8", "iPhone10,5": "iPhone 8 Plus", "iPhone10,6": "iPhone X", "iPhone11,2": "iPhone XS", "iPhone11,4": "iPhone XS Max", "iPhone11,6": "iPhone XS Max", "iPhone11,8": "iPhone XR", "iPhone12,1": "iPhone 11", "iPhone12,3": "iPhone 11 Pro", "iPhone12,5": "iPhone 11 Pro Max", "iPhone12,8": "iPhone SE 2nd Gen", "iPhone13,1": "iPhone 12 Mini", "iPhone13,2": "iPhone 12", "iPhone13,3": "iPhone 12 Pro", "iPhone13,4": "iPhone 12 Pro Max", "iPhone14,2": "iPhone 13 Pro", "iPhone14,3": "iPhone 13 Pro Max", "iPhone14,4": "iPhone 13 Mini", "iPhone14,5": "iPhone 13", "iPhone14,6": "iPhone SE 3rd Gen", "iPhone14,7": "iPhone 14", "iPhone14,8": "iPhone 14 Plus", "iPhone15,2": "iPhone 14 Pro", "iPhone15,3": "iPhone 14 Pro Max", "iPhone15,4": "iPhone 15", "iPhone15,5": "iPhone 15 Plus", "iPhone16,1": "iPhone 15 Pro", "iPhone16,2": "iPhone 15 Pro Max", "iPhone17,1": "iPhone 16 Pro", "iPhone17,2": "iPhone 16 Pro Max", "iPhone17,3": "iPhone 16", "iPhone17,4": "iPhone 16 Plus", "iPhone17,5": "iPhone 16e", "iPhone2,1": "iPhone 3GS", "iPhone3,1": "iPhone 4", "iPhone3,2": "iPhone 4", "iPhone3,3": "iPhone 4", "iPhone4,1": "iPhone 4S", "iPhone5,1": "iPhone 5", "iPhone5,2": "iPhone 5", "iPhone5,3": "iPhone 5C", "iPhone5,4": "iPhone 5C", "iPhone6,1": "iPhone 5S", "iPhone6,2": "iPhone 5S", "iPhone7,1": "iPhone 6 Plus", "iPhone7,2": "iPhone 6", "iPhone8,1": "iPhone 6s", "iPhone8,2": "iPhone 6s Plus", "iPhone8,4": "iPhone SE", "iPhone9,1": "iPhone 7", "iPhone9,2": "iPhone 7 Plus", "iPhone9,3": "iPhone 7", "iPhone9,4": "iPhone 7 Plus", "iPod1,1": "1st Gen iPod", "iPod2,1": "2nd Gen iPod", "iPod3,1": "3rd Gen iPod", "iPod4,1": "4th Gen iPod", "iPod5,1": "5th Gen iPod", "iPod7,1": "6th Gen iPod", "iPod9,1": "7th Gen iPod", "x86_64": "iPhone Simulator", } # Cache for Android device names CACHE_FILE = os.path.join( os.path.dirname(os.path.abspath(__file__)), "android_device_cache.json" ) android_device_cache = {} # Load cache from file if it exists def load_cache(): global android_device_cache try: if os.path.exists(CACHE_FILE): with open(CACHE_FILE, "r") as f: android_device_cache = json.load(f) print(f"Loaded {len(android_device_cache)} Android device names from cache") except Exception as e: print(f"Error loading Android device cache: {str(e)}") android_device_cache = {} # Save cache to file def save_cache(): try: with open(CACHE_FILE, "w") as f: json.dump(android_device_cache, f, indent=2) except Exception as e: print(f"Error saving Android device cache: {str(e)}") # Load cache on module import load_cache() @lru_cache(maxsize=1024) def get_device_name(manufacturer, device_id, default_device_name): """ Get the normalized device name from a device ID like iPad16,1 Args: device_id (str): The device identifier (e.g., iPad16,1) Returns: str: The normalized device name without connectivity info """ if manufacturer == "Apple": return apple_device_mapping.get(device_id, default_device_name) else: return get_android_model_name(manufacturer, device_id) def get_android_model_name(manufacturer, model): """ Get a user-friendly Android device name from cache or GitHub repository Args: manufacturer (str): Device manufacturer model (str): Device model Returns: str: User-friendly device name, or original model if not found """ # https://github.com/openhoangnc/android_model_name # Create a cache key by combining manufacturer and model cache_key = f"{manufacturer.lower()}:{model.lower()}" # Check if we already have this device in our cache if cache_key in android_device_cache: # Return the cached result return android_device_cache[cache_key] # If not in cache, fetch from GitHub repo_url = "https://raw.githubusercontent.com/openhoangnc/android_model_name/main" # Prepare the URL path components if not manufacturer: # Return the model if manufacturer is empty return model first_letter = manufacturer[0].lower() safe_manufacturer = quote(manufacturer.lower()) safe_model = quote(model.replace("/", "-").lower()) if model else "" # Build the query URL query_url = f"{repo_url}/{first_letter}/{safe_manufacturer}/{safe_model}" try: # Fetch the device name from the repository response = requests.get(query_url, timeout=3) if response.status_code == 200: device_name = response.text.strip() # Cache the result android_device_cache[cache_key] = device_name # Save cache periodically (every 10 new entries) if len(android_device_cache) % 10 == 0: save_cache() return device_name return model except Exception as e: print(f"Error fetching Android model name: {str(e)}") return model # Make sure to save the cache when the program exits import atexit atexit.register(save_cache)