291 lines
9.5 KiB
Python
291 lines
9.5 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
Configuration-based Snapshot Downloader for ParentZone
|
||
|
|
|
||
|
|
This script reads configuration from a JSON file and downloads snapshots (daily events)
|
||
|
|
from the ParentZone API with pagination support, generating a comprehensive HTML report.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import asyncio
|
||
|
|
import json
|
||
|
|
import logging
|
||
|
|
import os
|
||
|
|
from datetime import datetime, timedelta
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
# Import the snapshot downloader
|
||
|
|
try:
|
||
|
|
from snapshot_downloader import SnapshotDownloader
|
||
|
|
except ImportError:
|
||
|
|
print("Error: snapshot_downloader.py not found. Please ensure it's in the same directory.")
|
||
|
|
exit(1)
|
||
|
|
|
||
|
|
|
||
|
|
class ConfigSnapshotDownloader:
|
||
|
|
def __init__(self, config_file: str):
|
||
|
|
"""
|
||
|
|
Initialize the downloader with configuration from a JSON file.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
config_file: Path to the JSON configuration file
|
||
|
|
"""
|
||
|
|
self.config = self.load_config(config_file)
|
||
|
|
self.setup_logging()
|
||
|
|
|
||
|
|
# Create the underlying snapshot downloader
|
||
|
|
self.downloader = SnapshotDownloader(
|
||
|
|
api_url=self.config.get('api_url', 'https://api.parentzone.me'),
|
||
|
|
output_dir=self.config.get('output_dir', 'snapshots'),
|
||
|
|
api_key=self.config.get('api_key'),
|
||
|
|
email=self.config.get('email'),
|
||
|
|
password=self.config.get('password')
|
||
|
|
)
|
||
|
|
|
||
|
|
def load_config(self, config_file: str) -> dict:
|
||
|
|
"""Load configuration from JSON file."""
|
||
|
|
try:
|
||
|
|
with open(config_file, 'r') as f:
|
||
|
|
config = json.load(f)
|
||
|
|
|
||
|
|
# Validate required authentication
|
||
|
|
has_api_key = 'api_key' in config and config['api_key']
|
||
|
|
has_credentials = 'email' in config and 'password' in config and config['email'] and config['password']
|
||
|
|
|
||
|
|
if not has_api_key and not has_credentials:
|
||
|
|
raise ValueError("Either 'api_key' or both 'email' and 'password' must be provided in config")
|
||
|
|
|
||
|
|
# Set defaults for optional fields
|
||
|
|
config.setdefault('api_url', 'https://api.parentzone.me')
|
||
|
|
config.setdefault('output_dir', 'snapshots')
|
||
|
|
config.setdefault('type_ids', [15])
|
||
|
|
config.setdefault('max_pages', None)
|
||
|
|
|
||
|
|
# Set default date range (last year) if not specified
|
||
|
|
if 'date_from' not in config or not config['date_from']:
|
||
|
|
config['date_from'] = (datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d")
|
||
|
|
|
||
|
|
if 'date_to' not in config or not config['date_to']:
|
||
|
|
config['date_to'] = datetime.now().strftime("%Y-%m-%d")
|
||
|
|
|
||
|
|
return config
|
||
|
|
|
||
|
|
except FileNotFoundError:
|
||
|
|
raise FileNotFoundError(f"Configuration file not found: {config_file}")
|
||
|
|
except json.JSONDecodeError as e:
|
||
|
|
raise ValueError(f"Invalid JSON in configuration file: {e}")
|
||
|
|
|
||
|
|
def setup_logging(self):
|
||
|
|
"""Setup logging configuration."""
|
||
|
|
output_dir = Path(self.config['output_dir'])
|
||
|
|
output_dir.mkdir(exist_ok=True)
|
||
|
|
log_file = output_dir / 'snapshots.log'
|
||
|
|
|
||
|
|
logging.basicConfig(
|
||
|
|
level=logging.INFO,
|
||
|
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
||
|
|
handlers=[
|
||
|
|
logging.FileHandler(log_file),
|
||
|
|
logging.StreamHandler()
|
||
|
|
]
|
||
|
|
)
|
||
|
|
self.logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
async def download_snapshots(self) -> Path:
|
||
|
|
"""
|
||
|
|
Download snapshots using the configuration settings.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Path to the generated HTML file
|
||
|
|
"""
|
||
|
|
self.logger.info("Starting snapshot download with configuration")
|
||
|
|
self.logger.info(f"Date range: {self.config['date_from']} to {self.config['date_to']}")
|
||
|
|
self.logger.info(f"Type IDs: {self.config['type_ids']}")
|
||
|
|
self.logger.info(f"Output directory: {self.config['output_dir']}")
|
||
|
|
|
||
|
|
if self.config.get('max_pages'):
|
||
|
|
self.logger.info(f"Max pages limit: {self.config['max_pages']}")
|
||
|
|
|
||
|
|
try:
|
||
|
|
html_file = await self.downloader.download_snapshots(
|
||
|
|
type_ids=self.config['type_ids'],
|
||
|
|
date_from=self.config['date_from'],
|
||
|
|
date_to=self.config['date_to'],
|
||
|
|
max_pages=self.config.get('max_pages')
|
||
|
|
)
|
||
|
|
|
||
|
|
return html_file
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
self.logger.error(f"Error during snapshot download: {e}")
|
||
|
|
raise
|
||
|
|
|
||
|
|
def print_config_summary(self):
|
||
|
|
"""Print a summary of the current configuration."""
|
||
|
|
print("=" * 60)
|
||
|
|
print("SNAPSHOT DOWNLOADER CONFIGURATION")
|
||
|
|
print("=" * 60)
|
||
|
|
print(f"API URL: {self.config['api_url']}")
|
||
|
|
print(f"Output Directory: {self.config['output_dir']}")
|
||
|
|
print(f"Date From: {self.config['date_from']}")
|
||
|
|
print(f"Date To: {self.config['date_to']}")
|
||
|
|
print(f"Type IDs: {self.config['type_ids']}")
|
||
|
|
|
||
|
|
auth_method = "API Key" if self.config.get('api_key') else "Email/Password"
|
||
|
|
print(f"Authentication: {auth_method}")
|
||
|
|
|
||
|
|
if self.config.get('max_pages'):
|
||
|
|
print(f"Max Pages: {self.config['max_pages']}")
|
||
|
|
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
|
||
|
|
def create_example_config():
|
||
|
|
"""Create an example configuration file."""
|
||
|
|
example_config = {
|
||
|
|
"api_url": "https://api.parentzone.me",
|
||
|
|
"output_dir": "./snapshots",
|
||
|
|
"type_ids": [15],
|
||
|
|
"date_from": "2024-01-01",
|
||
|
|
"date_to": "2024-12-31",
|
||
|
|
"max_pages": null,
|
||
|
|
"api_key": "your-api-key-here",
|
||
|
|
"email": "your-email@example.com",
|
||
|
|
"password": "your-password-here"
|
||
|
|
}
|
||
|
|
|
||
|
|
config_file = Path("snapshot_config_example.json")
|
||
|
|
with open(config_file, 'w') as f:
|
||
|
|
json.dump(example_config, f, indent=2)
|
||
|
|
|
||
|
|
print(f"✅ Example configuration created: {config_file}")
|
||
|
|
print("📝 Edit the file with your credentials and settings")
|
||
|
|
return config_file
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
parser = argparse.ArgumentParser(
|
||
|
|
description="Download ParentZone snapshots using configuration file",
|
||
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||
|
|
epilog="""
|
||
|
|
Examples:
|
||
|
|
# Use existing config file
|
||
|
|
python3 config_snapshot_downloader.py --config snapshot_config.json
|
||
|
|
|
||
|
|
# Create example config file
|
||
|
|
python3 config_snapshot_downloader.py --create-example
|
||
|
|
|
||
|
|
# Show config summary before downloading
|
||
|
|
python3 config_snapshot_downloader.py --config snapshot_config.json --show-config
|
||
|
|
|
||
|
|
Configuration file format:
|
||
|
|
{
|
||
|
|
"api_url": "https://api.parentzone.me",
|
||
|
|
"output_dir": "./snapshots",
|
||
|
|
"type_ids": [15],
|
||
|
|
"date_from": "2024-01-01",
|
||
|
|
"date_to": "2024-12-31",
|
||
|
|
"max_pages": null,
|
||
|
|
"api_key": "your-api-key-here",
|
||
|
|
"email": "your-email@example.com",
|
||
|
|
"password": "your-password-here"
|
||
|
|
}
|
||
|
|
|
||
|
|
Notes:
|
||
|
|
- Either 'api_key' OR both 'email' and 'password' are required
|
||
|
|
- 'date_from' and 'date_to' default to last year if not specified
|
||
|
|
- 'type_ids' defaults to [15] (snapshot type)
|
||
|
|
- 'max_pages' limits pages fetched (useful for testing)
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
|
||
|
|
parser.add_argument(
|
||
|
|
'--config',
|
||
|
|
help='Path to the JSON configuration file'
|
||
|
|
)
|
||
|
|
|
||
|
|
parser.add_argument(
|
||
|
|
'--create-example',
|
||
|
|
action='store_true',
|
||
|
|
help='Create an example configuration file and exit'
|
||
|
|
)
|
||
|
|
|
||
|
|
parser.add_argument(
|
||
|
|
'--show-config',
|
||
|
|
action='store_true',
|
||
|
|
help='Show configuration summary before downloading'
|
||
|
|
)
|
||
|
|
|
||
|
|
parser.add_argument(
|
||
|
|
'--debug',
|
||
|
|
action='store_true',
|
||
|
|
help='Enable debug mode with detailed server response logging'
|
||
|
|
)
|
||
|
|
|
||
|
|
args = parser.parse_args()
|
||
|
|
|
||
|
|
# Handle create example
|
||
|
|
if args.create_example:
|
||
|
|
create_example_config()
|
||
|
|
return 0
|
||
|
|
|
||
|
|
# Validate config argument
|
||
|
|
if not args.config:
|
||
|
|
print("Error: --config argument is required (or use --create-example)")
|
||
|
|
print("Run with --help for more information")
|
||
|
|
return 1
|
||
|
|
|
||
|
|
try:
|
||
|
|
# Create downloader
|
||
|
|
downloader = ConfigSnapshotDownloader(args.config)
|
||
|
|
|
||
|
|
# Show configuration if requested
|
||
|
|
if args.show_config:
|
||
|
|
downloader.print_config_summary()
|
||
|
|
print()
|
||
|
|
|
||
|
|
# Enable debug mode if requested
|
||
|
|
if args.debug:
|
||
|
|
print("🔍 DEBUG MODE ENABLED - Detailed server responses will be printed")
|
||
|
|
# Set debug flag on the underlying downloader
|
||
|
|
downloader.downloader.debug_mode = True
|
||
|
|
|
||
|
|
# Download snapshots
|
||
|
|
html_file = asyncio.run(downloader.download_snapshots())
|
||
|
|
|
||
|
|
if html_file:
|
||
|
|
print("\n" + "=" * 60)
|
||
|
|
print("✅ SUCCESS!")
|
||
|
|
print("=" * 60)
|
||
|
|
print(f"📄 HTML Report: {html_file}")
|
||
|
|
print(f"📁 Open the file in your browser to view the snapshots")
|
||
|
|
print("🎯 The report includes:")
|
||
|
|
print(" • All snapshots with descriptions and metadata")
|
||
|
|
print(" • Images and attachments (if any)")
|
||
|
|
print(" • Search and filtering capabilities")
|
||
|
|
print(" • Interactive collapsible sections")
|
||
|
|
print("=" * 60)
|
||
|
|
else:
|
||
|
|
print("⚠️ No snapshots were found for the specified period")
|
||
|
|
print("💡 Try adjusting the date range in your configuration")
|
||
|
|
|
||
|
|
except KeyboardInterrupt:
|
||
|
|
print("\n⚠️ Download interrupted by user")
|
||
|
|
return 1
|
||
|
|
except FileNotFoundError as e:
|
||
|
|
print(f"❌ Configuration file error: {e}")
|
||
|
|
print("💡 Use --create-example to generate a template")
|
||
|
|
return 1
|
||
|
|
except ValueError as e:
|
||
|
|
print(f"❌ Configuration error: {e}")
|
||
|
|
return 1
|
||
|
|
except Exception as e:
|
||
|
|
print(f"❌ Download failed: {e}")
|
||
|
|
return 1
|
||
|
|
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
exit(main())
|