Files
eboek.info-scraper/gui_main.py
Louis Mylle c6a69c6ada feat: Add Bookys scraper alongside EBoek with startup mode chooser
Adds a second scraper for bookys-ebooks.com that harvests direct 1fichier
download URLs into a .txt file. The existing EBoek scraper core is unchanged;
only the GUI gained a Mode menu entry to switch between the two.

Bookys specifics:
- Visible (never headless) browser, since the site is behind Cloudflare.
  The challenge is auto-detected: the scraper polls for real listing items
  and continues on its own once it clears, no button needed.
- Persistent Chrome profile in ~/.eboek_scraper/bookys_chrome_profile so the
  Cloudflare clearance cookie survives between runs. Removed the hardcoded
  user-agent override, which claimed Chrome/120 against a real Chrome/150 and
  invalidated that cookie on every launch.
- Pop-up ads are avoided by never clicking host links: hrefs are read and
  navigated to directly, with window.open neutered via CDP as a backstop.
- Host links are read with textContent rather than Selenium's .text, which
  returns empty for these elements because they sit in a collapsed container.
- Only 1fichier hosts are followed; other hosts on a page are ignored.

Build configs list the new modules as hidden imports in all four places.
gui_main imports the Bookys window lazily, so PyInstaller's static analysis
would otherwise miss it and the .exe would crash on switching modes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVJc2XuTvqBHSuqotetseS
2026-07-19 20:05:49 +02:00

250 lines
8.1 KiB
Python

#!/usr/bin/env python3
"""
EBoek.info Scraper GUI Application
Main entry point for the PyQt5 GUI version of the EBoek.info scraper.
"""
import sys
import os
import traceback
import warnings
from pathlib import Path
# Suppress urllib3 OpenSSL warnings on macOS
warnings.filterwarnings("ignore", message="urllib3 v2 only supports OpenSSL 1.1.1+")
warnings.filterwarnings("ignore", category=UserWarning, module="urllib3")
# Ensure we can import PyQt5
try:
from PyQt5.QtWidgets import QApplication, QMessageBox
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
except ImportError as e:
print("Error: PyQt5 is not installed.")
print("Please run: pip install PyQt5")
print(f"Import error: {e}")
sys.exit(1)
# Add the project root to Python path
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
# Import our GUI components
try:
from gui.main_window import MainWindow
from core.credentials import CredentialManager
except ImportError as e:
print(f"Error importing application modules: {e}")
print("Please ensure all required files are present.")
traceback.print_exc()
sys.exit(1)
class EBoekScraperApp(QApplication):
"""
Main application class for the EBoek.info Scraper GUI.
"""
def __init__(self, argv):
super().__init__(argv)
self.setApplicationName("EBoek.info Scraper")
self.setApplicationVersion("2.0")
self.setOrganizationName("EBoek Scraper")
# Set application icon if available
self.set_application_icon()
# Handle exceptions
sys.excepthook = self.handle_exception
self.main_window = None # EBoek window
self.bookys_window = None # Bookys window
def set_application_icon(self):
"""Set the application icon if available."""
# You can add an icon file here if desired
# icon_path = project_root / "resources" / "icon.png"
# if icon_path.exists():
# self.setWindowIcon(QIcon(str(icon_path)))
pass
def handle_exception(self, exc_type, exc_value, exc_traceback):
"""Handle uncaught exceptions."""
if issubclass(exc_type, KeyboardInterrupt):
# Allow Ctrl+C to exit gracefully
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
# Log the exception
error_msg = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback))
print("Uncaught exception:")
print(error_msg)
# Show error dialog to user
if self.main_window:
try:
QMessageBox.critical(
self.main_window,
"Unexpected Error",
f"An unexpected error occurred:\n\n{str(exc_value)}\n\n"
f"The application may need to be restarted.\n\n"
f"Error type: {exc_type.__name__}"
)
except:
# If we can't show the dialog, just print
print("Could not display error dialog")
def initialize(self):
"""Initialize the application."""
try:
# Check system requirements
self.check_requirements()
# Ask which site to scrape, then show the matching window.
from gui.startup_dialog import StartupDialog
chooser = StartupDialog()
chooser.exec_()
if chooser.choice == StartupDialog.BOOKYS:
self.show_bookys()
else:
# Default to EBoek (also covers the dialog being closed).
self.show_eboek()
return True
except Exception as e:
print(f"Error initializing application: {e}")
traceback.print_exc()
# Try to show error dialog
try:
msg = QMessageBox()
msg.setIcon(QMessageBox.Critical)
msg.setWindowTitle("Initialization Error")
msg.setText(f"Could not initialize the application:\n\n{str(e)}")
msg.setDetailedText(traceback.format_exc())
msg.exec_()
except:
print("Could not display initialization error dialog")
return False
def show_eboek(self):
"""Show the EBoek window, hiding the Bookys one if present."""
if self.main_window is None:
self.main_window = MainWindow()
if self.bookys_window is not None:
self.bookys_window.hide()
self.main_window.show()
self.main_window.raise_()
self.main_window.activateWindow()
def show_bookys(self):
"""Show the Bookys window, hiding the EBoek one if present."""
from gui.bookys_window import BookysWindow
if self.bookys_window is None:
self.bookys_window = BookysWindow()
if self.main_window is not None:
self.main_window.hide()
self.bookys_window.show()
self.bookys_window.raise_()
self.bookys_window.activateWindow()
def check_requirements(self):
"""Check system requirements and dependencies."""
errors = []
# Check Python version
if sys.version_info < (3, 6):
errors.append(f"Python 3.6+ required (found {sys.version_info.major}.{sys.version_info.minor})")
# Check required modules
required_modules = ['selenium', 'urllib3']
missing_modules = []
for module in required_modules:
try:
__import__(module)
except ImportError:
missing_modules.append(module)
if missing_modules:
errors.append(f"Missing required modules: {', '.join(missing_modules)}")
# Check Chrome/Chromium availability (basic check)
chrome_paths = [
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", # macOS
"/usr/bin/google-chrome", # Linux
"/usr/bin/chromium-browser", # Linux (Chromium)
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", # Windows
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe", # Windows 32-bit
]
chrome_found = any(Path(path).exists() for path in chrome_paths)
if not chrome_found:
# This is a warning, not an error
print("Warning: Chrome browser not detected in standard locations.")
print("Make sure Google Chrome is installed for the scraper to work.")
if errors:
error_text = "System requirement errors:\n\n" + "\n".join(f"{error}" for error in errors)
error_text += "\n\nPlease install missing requirements and try again."
raise RuntimeError(error_text)
def show_startup_error(title, message):
"""Show a startup error dialog without a main window."""
app = QApplication.instance()
if not app:
app = QApplication(sys.argv)
msg = QMessageBox()
msg.setIcon(QMessageBox.Critical)
msg.setWindowTitle(title)
msg.setText(message)
msg.exec_()
def main():
"""Main entry point for the GUI application."""
# Set up high DPI support
if hasattr(Qt, 'AA_EnableHighDpiScaling'):
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
if hasattr(Qt, 'AA_UseHighDpiPixmaps'):
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
# Create the application
try:
app = EBoekScraperApp(sys.argv)
# Initialize and run
if app.initialize():
# Run the application
sys.exit(app.exec_())
else:
print("Application initialization failed")
sys.exit(1)
except Exception as e:
print(f"Fatal error starting application: {e}")
traceback.print_exc()
# Try to show error dialog
try:
show_startup_error(
"Startup Error",
f"Could not start the EBoek.info Scraper:\n\n{str(e)}\n\n"
f"Please check the installation and try again."
)
except:
print("Could not display startup error dialog")
sys.exit(1)
if __name__ == "__main__":
main()