""" Simple JSON-based credential storage system for EBoek.info scraper. """ import json import os from pathlib import Path import stat class CredentialManager: """ Manages storage and retrieval of user credentials in a JSON config file. Credentials are stored in the user's home directory in a hidden folder with appropriate file permissions for basic security. """ def __init__(self, app_name="eboek_scraper"): """ Initialize the credential manager. Args: app_name (str): Application name for config directory """ self.app_name = app_name self.config_dir = Path.home() / f".{app_name}" self.config_file = self.config_dir / "config.json" self._ensure_config_dir() def _ensure_config_dir(self): """ Ensure the configuration directory exists with appropriate permissions. """ try: if not self.config_dir.exists(): self.config_dir.mkdir(mode=0o700, exist_ok=True) # Only user can read/write/execute # Ensure directory has correct permissions (user only) if os.name != 'nt': # Unix-like systems (macOS, Linux) os.chmod(self.config_dir, stat.S_IRWXU) # 700 permissions except Exception as e: # If we can't create the config directory, fall back to current directory self.config_dir = Path(".") self.config_file = self.config_dir / f".{self.app_name}_config.json" def _load_config(self): """ Load the configuration file. Returns: dict: Configuration data, empty dict if file doesn't exist """ try: if self.config_file.exists(): with open(self.config_file, 'r', encoding='utf-8') as f: return json.load(f) except (json.JSONDecodeError, IOError, PermissionError) as e: # If there's any error reading the config, return empty dict pass return {} def _save_config(self, config_data): """ Save configuration data to file. Args: config_data (dict): Configuration data to save Returns: bool: True if saved successfully, False otherwise """ try: with open(self.config_file, 'w', encoding='utf-8') as f: json.dump(config_data, f, indent=2, ensure_ascii=False) # Set file permissions to be readable/writable by user only if os.name != 'nt': # Unix-like systems os.chmod(self.config_file, stat.S_IRUSR | stat.S_IWUSR) # 600 permissions return True except (IOError, PermissionError) as e: return False def save_credentials(self, username, password, remember=True): """ Save user credentials to the config file. Args: username (str): EBoek.info username password (str): EBoek.info password remember (bool): Whether to save credentials for future use Returns: bool: True if saved successfully, False otherwise """ if not remember: # If remember is False, just clear any existing credentials return self.clear_credentials() try: config = self._load_config() config['credentials'] = { 'username': username, 'password': password, 'saved_at': str(Path.home()), # Just to know which user saved it } return self._save_config(config) except Exception as e: return False def load_credentials(self): """ Load stored credentials. Returns: dict or None: Dictionary with 'username' and 'password' keys if found, None if no credentials are stored """ try: config = self._load_config() credentials = config.get('credentials') if credentials and 'username' in credentials and 'password' in credentials: return { 'username': credentials['username'], 'password': credentials['password'] } except Exception as e: pass return None def has_saved_credentials(self): """ Check if there are saved credentials available. Returns: bool: True if credentials are available, False otherwise """ return self.load_credentials() is not None def get_saved_username(self): """ Get the saved username without the password. Returns: str or None: Saved username if available, None otherwise """ credentials = self.load_credentials() return credentials['username'] if credentials else None def clear_credentials(self): """ Remove stored credentials from the config file. Returns: bool: True if cleared successfully, False otherwise """ try: config = self._load_config() if 'credentials' in config: del config['credentials'] return self._save_config(config) return True # No credentials to clear is success except Exception as e: return False def validate_credentials(self, username, password): """ Basic validation of credential format. Args: username (str): Username to validate password (str): Password to validate Returns: dict: Validation result with 'valid' bool and 'errors' list """ errors = [] if not username or not username.strip(): errors.append("Username cannot be empty") elif len(username.strip()) < 2: errors.append("Username must be at least 2 characters") if not password or not password.strip(): errors.append("Password cannot be empty") elif len(password) < 3: errors.append("Password must be at least 3 characters") return { 'valid': len(errors) == 0, 'errors': errors } def get_config_file_path(self): """ Get the path to the configuration file. Returns: Path: Path to the config file """ return self.config_file def save_app_settings(self, settings): """ Save application settings (non-credential settings). Args: settings (dict): Application settings to save Returns: bool: True if saved successfully, False otherwise """ try: config = self._load_config() config['app_settings'] = settings return self._save_config(config) except Exception as e: return False def load_app_settings(self): """ Load application settings (non-credential settings). Returns: dict: Application settings, empty dict if none saved """ try: config = self._load_config() return config.get('app_settings', {}) except Exception as e: return {} def get_default_settings(self): """ Get default application settings. Returns: dict: Default settings """ return { 'headless_mode': True, 'verbose_logging': False, 'auto_save_credentials': True, 'download_path': str(Path.home() / "Downloads"), 'default_start_page': 1, 'default_end_page': 1, 'scraping_mode': 0 # 0=All Comics, 1=Latest Comics } def export_settings(self, export_path): """ Export settings (excluding credentials) to a file. Args: export_path (str or Path): Path to export settings to Returns: bool: True if exported successfully, False otherwise """ try: config = self._load_config() # Remove credentials from export export_config = {k: v for k, v in config.items() if k != 'credentials'} with open(export_path, 'w', encoding='utf-8') as f: json.dump(export_config, f, indent=2, ensure_ascii=False) return True except Exception as e: return False def import_settings(self, import_path): """ Import settings (excluding credentials) from a file. Args: import_path (str or Path): Path to import settings from Returns: bool: True if imported successfully, False otherwise """ try: with open(import_path, 'r', encoding='utf-8') as f: imported_config = json.load(f) # Don't import credentials for security if 'credentials' in imported_config: del imported_config['credentials'] # Merge with existing config config = self._load_config() config.update(imported_config) return self._save_config(config) except Exception as e: return False