feat: Add installation scripts for Windows and Unix-based systems

- Created `install_and_run.bat` for Windows installation and setup.
- Created `install_and_run.sh` for Unix-based systems installation and setup.
- Removed `main.py` as it is no longer needed.
- Updated `requirements.txt` to specify package versions and added PyQt5.
- Deleted `start.bat` as it is redundant.
- Added unit tests for core functionality and scraping modes.
- Implemented input validation utilities in `utils/validators.py`.
- Added support for dual scraping modes in the scraper.
This commit is contained in:
Louis Mylle
2026-01-10 14:45:00 +01:00
parent 5f2fca226b
commit ea4cab15c3
19 changed files with 3731 additions and 335 deletions

309
core/credentials.py Normal file
View File

@@ -0,0 +1,309 @@
"""
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