Adding src/ folder? no idea what happened
This commit is contained in:
		
							
								
								
									
										245
									
								
								src/app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								src/app.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,245 @@
 | 
				
			|||||||
 | 
					from nicegui import ui, app
 | 
				
			||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import nhentai_manager  # Import the nhentai_manager module with core functionality
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Set up dark mode
 | 
				
			||||||
 | 
					ui.dark_mode().enable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Global reference to UI components
 | 
				
			||||||
 | 
					history_container = None
 | 
				
			||||||
 | 
					id_input = None
 | 
				
			||||||
 | 
					app.storage.status_changed = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def add_nhentai_id():
 | 
				
			||||||
 | 
					    """Handle adding a new nhentai ID for download"""
 | 
				
			||||||
 | 
					    id_value = id_input.value
 | 
				
			||||||
 | 
					    if not id_value:
 | 
				
			||||||
 | 
					        ui.notify('Please enter an ID', color='warning')
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Clear the input field after submission
 | 
				
			||||||
 | 
					    id_input.value = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Add to database
 | 
				
			||||||
 | 
					    nhentai_manager.db_add_download(id_value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Show notification
 | 
				
			||||||
 | 
					    ui.notify(f'Adding nhentai ID: {id_value}')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Refresh the download history display
 | 
				
			||||||
 | 
					    refresh_download_history()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def update_status_icon(status_icon, status, message=None):
 | 
				
			||||||
 | 
					    """Update the status icon based on download status"""
 | 
				
			||||||
 | 
					    if status == "pending":
 | 
				
			||||||
 | 
					        status_icon.name = 'circle'
 | 
				
			||||||
 | 
					        status_icon.classes('text-yellow-500', remove='text-green-500 text-red-500 text-blue-500')
 | 
				
			||||||
 | 
					    elif status == "downloading":
 | 
				
			||||||
 | 
					        status_icon.name = 'downloading'
 | 
				
			||||||
 | 
					        status_icon.classes('text-blue-500', remove='text-yellow-500 text-green-500 text-red-500')
 | 
				
			||||||
 | 
					    elif status == "success":
 | 
				
			||||||
 | 
					        status_icon.name = 'check_circle'
 | 
				
			||||||
 | 
					        status_icon.classes('text-green-500', remove='text-yellow-500 text-red-500 text-blue-500')
 | 
				
			||||||
 | 
					        ui.notify(f'Download completed successfully', color='positive')
 | 
				
			||||||
 | 
					    elif status == "error":
 | 
				
			||||||
 | 
					        status_icon.name = 'error'
 | 
				
			||||||
 | 
					        status_icon.classes('text-red-500', remove='text-yellow-500 text-green-500 text-blue-500')
 | 
				
			||||||
 | 
					        if message:
 | 
				
			||||||
 | 
					            ui.notify(f'Download failed: {message}', color='negative')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            ui.notify(f'Download failed', color='negative')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def safely_refresh_history():
 | 
				
			||||||
 | 
					    if app.storage.status_changed:
 | 
				
			||||||
 | 
					        refresh_download_history()
 | 
				
			||||||
 | 
					        app.storage.status_changed = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def on_status_change(nhentai_id, status, message=None):
 | 
				
			||||||
 | 
					    """Callback for status changes during download"""
 | 
				
			||||||
 | 
					    # We need to refresh the UI to reflect the changes
 | 
				
			||||||
 | 
					    app.storage.status_changed = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def start_download(nhentai_id, status_icon):
 | 
				
			||||||
 | 
					    """Start downloading a doujinshi"""
 | 
				
			||||||
 | 
					    # Update the UI status
 | 
				
			||||||
 | 
					    update_status_icon(status_icon, "downloading")
 | 
				
			||||||
 | 
					    ui.notify(f'Starting download for ID: {nhentai_id}', color='info')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Start the download in a background thread
 | 
				
			||||||
 | 
					    nhentai_manager.start_download_thread(nhentai_id, on_status_change)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Set up periodic UI refresh to check for status updates
 | 
				
			||||||
 | 
					    refresh_status_periodically(nhentai_id, status_icon)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def refresh_status_periodically(nhentai_id, status_icon):
 | 
				
			||||||
 | 
					    """Periodically check the download status and update the UI"""
 | 
				
			||||||
 | 
					    # Get current status from database
 | 
				
			||||||
 | 
					    status_info = nhentai_manager.db_get_download_status(nhentai_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if status_info:
 | 
				
			||||||
 | 
					        status = status_info['status']
 | 
				
			||||||
 | 
					        message = status_info['message']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Update the UI based on the status
 | 
				
			||||||
 | 
					        update_status_icon(status_icon, status, message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # If still downloading, check again in a second
 | 
				
			||||||
 | 
					        if status == "downloading":
 | 
				
			||||||
 | 
					            ui.timer(1.0, lambda: refresh_status_periodically(nhentai_id, status_icon), once=True)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        # If no status found, check again in a second
 | 
				
			||||||
 | 
					        ui.timer(1.0, lambda: refresh_status_periodically(nhentai_id, status_icon), once=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def refresh_download_history():
 | 
				
			||||||
 | 
					    """Refresh the download history display from the database"""
 | 
				
			||||||
 | 
					    # Clear current history
 | 
				
			||||||
 | 
					    history_container.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Get all downloads from database
 | 
				
			||||||
 | 
					    downloads = nhentai_manager.db_get_all_downloads()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not downloads:
 | 
				
			||||||
 | 
					        with history_container:
 | 
				
			||||||
 | 
					            ui.label('No download history yet').classes('text-gray-500 italic')
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Populate with data from database
 | 
				
			||||||
 | 
					    for download in downloads:
 | 
				
			||||||
 | 
					        nhentai_id = download['nhentai_id']
 | 
				
			||||||
 | 
					        status = download['status']
 | 
				
			||||||
 | 
					        added_time = datetime.fromisoformat(download['added_time']).strftime("%Y-%m-%d %H:%M:%S")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with history_container:
 | 
				
			||||||
 | 
					            item_row = ui.row().classes('w-full items-center')
 | 
				
			||||||
 | 
					            with item_row:
 | 
				
			||||||
 | 
					                # Set icon based on status
 | 
				
			||||||
 | 
					                if status == 'pending':
 | 
				
			||||||
 | 
					                    status_icon = ui.icon('circle').classes('text-yellow-500')
 | 
				
			||||||
 | 
					                elif status == 'downloading':
 | 
				
			||||||
 | 
					                    status_icon = ui.icon('downloading').classes('text-blue-500')
 | 
				
			||||||
 | 
					                elif status == 'success':
 | 
				
			||||||
 | 
					                    status_icon = ui.icon('check_circle').classes('text-green-500')
 | 
				
			||||||
 | 
					                elif status == 'error':
 | 
				
			||||||
 | 
					                    status_icon = ui.icon('error').classes('text-red-500')
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    status_icon = ui.icon('help').classes('text-gray-500')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ui.label(f'ID: {nhentai_id}').classes('text-lg ml-2')
 | 
				
			||||||
 | 
					                ui.label(f'Added: {added_time}').classes('text-sm text-gray-500 ml-auto')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Only show download button for pending or failed downloads
 | 
				
			||||||
 | 
					                if status in ['pending', 'error']:
 | 
				
			||||||
 | 
					                    download_btn = ui.button('Download',
 | 
				
			||||||
 | 
					                                          on_click=lambda id=nhentai_id, icon=status_icon: start_download(id, icon),
 | 
				
			||||||
 | 
					                                          color='primary').classes('ml-2').props('size=sm')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def show_settings():
 | 
				
			||||||
 | 
					    """Display settings dialog"""
 | 
				
			||||||
 | 
					    current_settings = nhentai_manager.get_settings()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with ui.dialog() as dialog, ui.card().classes('w-96'):
 | 
				
			||||||
 | 
					        ui.label('Download Settings').classes('text-xl font-bold')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Basic Settings
 | 
				
			||||||
 | 
					        ui.label('Basic Settings').classes('text-lg font-semibold mt-4')
 | 
				
			||||||
 | 
					        download_dir_input = ui.input('Download Directory', value=current_settings['download_dir']).classes('w-full')
 | 
				
			||||||
 | 
					        output_format_input = ui.input('Output Format', value=current_settings['output_format'],
 | 
				
			||||||
 | 
					                                     placeholder='e.g. [%i]%s').classes('w-full')
 | 
				
			||||||
 | 
					        ui.label('Supported: %i (ID), %s (subtitle), %t (title), %a (authors), %g (groups), %p (pretty name)').classes('text-xs text-gray-500')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Advanced Settings
 | 
				
			||||||
 | 
					        ui.label('Advanced Settings').classes('text-lg font-semibold mt-4')
 | 
				
			||||||
 | 
					        thread_count_input = ui.number('Thread Count', value=current_settings['thread_count'], min=1, max=20)
 | 
				
			||||||
 | 
					        timeout_input = ui.number('Timeout (seconds)', value=current_settings['timeout'], min=5, max=120)
 | 
				
			||||||
 | 
					        retry_count_input = ui.number('Retry Count', value=current_settings['retry_count'], min=0, max=10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Output Options
 | 
				
			||||||
 | 
					        ui.label('Output Options').classes('text-lg font-semibold mt-4')
 | 
				
			||||||
 | 
					        html_viewer_toggle = ui.switch('Generate HTML Viewer', value=current_settings['html_viewer'])
 | 
				
			||||||
 | 
					        cbz_toggle = ui.switch('Generate CBZ File', value=current_settings['generate_cbz'])
 | 
				
			||||||
 | 
					        pdf_toggle = ui.switch('Generate PDF File', value=current_settings['generate_pdf'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Authentication (for bypassing Cloudflare)
 | 
				
			||||||
 | 
					        ui.label('Authentication (to bypass Cloudflare)').classes('text-lg font-semibold mt-4')
 | 
				
			||||||
 | 
					        cookie_input = ui.input('Cookie', value=current_settings['cookie'],
 | 
				
			||||||
 | 
					                              placeholder='csrftoken=TOKEN; sessionid=ID; cf_clearance=CLOUDFLARE').classes('w-full')
 | 
				
			||||||
 | 
					        useragent_input = ui.input('User Agent', value=current_settings['user_agent'],
 | 
				
			||||||
 | 
					                                  placeholder='Mozilla/5.0 ...').classes('w-full')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with ui.row():
 | 
				
			||||||
 | 
					            ui.button('Cancel', on_click=dialog.close).props('outline')
 | 
				
			||||||
 | 
					            ui.button('Save', on_click=lambda: save_settings(
 | 
				
			||||||
 | 
					                download_dir_input.value,
 | 
				
			||||||
 | 
					                output_format_input.value,
 | 
				
			||||||
 | 
					                cookie_input.value,
 | 
				
			||||||
 | 
					                useragent_input.value,
 | 
				
			||||||
 | 
					                html_viewer_toggle.value,
 | 
				
			||||||
 | 
					                cbz_toggle.value,
 | 
				
			||||||
 | 
					                pdf_toggle.value,
 | 
				
			||||||
 | 
					                thread_count_input.value,
 | 
				
			||||||
 | 
					                timeout_input.value,
 | 
				
			||||||
 | 
					                retry_count_input.value,
 | 
				
			||||||
 | 
					                dialog
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					    dialog.open()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def save_settings(download_dir, output_format, cookie, useragent,
 | 
				
			||||||
 | 
					                html_viewer, generate_cbz, generate_pdf, thread_count, timeout, retry_count, dialog):
 | 
				
			||||||
 | 
					    """Save settings to the nhentai_manager module"""
 | 
				
			||||||
 | 
					    # Update settings
 | 
				
			||||||
 | 
					    new_settings = {
 | 
				
			||||||
 | 
					        "download_dir": download_dir,
 | 
				
			||||||
 | 
					        "output_format": output_format,
 | 
				
			||||||
 | 
					        "cookie": cookie,
 | 
				
			||||||
 | 
					        "user_agent": useragent,
 | 
				
			||||||
 | 
					        "html_viewer": html_viewer,
 | 
				
			||||||
 | 
					        "generate_cbz": generate_cbz,
 | 
				
			||||||
 | 
					        "generate_pdf": generate_pdf,
 | 
				
			||||||
 | 
					        "thread_count": thread_count,
 | 
				
			||||||
 | 
					        "timeout": timeout,
 | 
				
			||||||
 | 
					        "retry_count": retry_count
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    nhentai_manager.update_settings(new_settings)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ui.notify('Settings saved', color='positive')
 | 
				
			||||||
 | 
					    dialog.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def setup_ui():
 | 
				
			||||||
 | 
					    """Set up the main UI components"""
 | 
				
			||||||
 | 
					    global history_container, id_input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with ui.header().classes('bg-primary'):
 | 
				
			||||||
 | 
					        ui.label('nhentai Downloader').classes('text-xl font-bold')
 | 
				
			||||||
 | 
					        ui.space()
 | 
				
			||||||
 | 
					        ui.button('Settings', on_click=show_settings).props('icon=settings flat')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Input and button row
 | 
				
			||||||
 | 
					    with ui.row().classes('w-full items-center mt-4'):
 | 
				
			||||||
 | 
					        id_input = ui.input(label='ID:').classes('mr-2')
 | 
				
			||||||
 | 
					        ui.button('Add ID!', on_click=add_nhentai_id).props('icon=add')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # History section
 | 
				
			||||||
 | 
					    ui.label('History').classes('text-xl font-bold mt-4 mb-2')
 | 
				
			||||||
 | 
					    history_container = ui.column().classes('w-full border rounded-lg p-2')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Add some instructions initially
 | 
				
			||||||
 | 
					    with history_container:
 | 
				
			||||||
 | 
					        ui.label('Enter an ID and click "Add ID!" to see the history').classes('text-gray-500 italic')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Add footer with help info
 | 
				
			||||||
 | 
					    with ui.footer().classes('bg-gray-100 dark:bg-gray-800 text-sm'):
 | 
				
			||||||
 | 
					        ui.label('Using nhentai CLI tool. Need help? Check the GitHub page:')
 | 
				
			||||||
 | 
					        ui.link('https://github.com/RicterZ/nhentai', 'https://github.com/RicterZ/nhentai').classes('ml-1')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Add a periodic timer to safely refresh the UI
 | 
				
			||||||
 | 
					    ui.timer(1.0, safely_refresh_history)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Initialize UI and load existing downloads on startup
 | 
				
			||||||
 | 
					@app.on_startup
 | 
				
			||||||
 | 
					def on_startup():
 | 
				
			||||||
 | 
					    refresh_download_history()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ in {"__main__", "__mp_main__"}:
 | 
				
			||||||
 | 
					    setup_ui()
 | 
				
			||||||
 | 
					    ui.run()
 | 
				
			||||||
							
								
								
									
										259
									
								
								src/nhentai_manager.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										259
									
								
								src/nhentai_manager.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,259 @@
 | 
				
			|||||||
 | 
					from statistics import stdev
 | 
				
			||||||
 | 
					import sqlite3
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Configuration
 | 
				
			||||||
 | 
					DOWNLOAD_DIR = os.path.expanduser("out")
 | 
				
			||||||
 | 
					OUTPUT_FORMAT = "[%i]%s"  # Default format for folder naming
 | 
				
			||||||
 | 
					COOKIE = "session-affinity=1742247176.302.46.898572|2968378f2272707dac237fc5e1f12aaf; csrftoken=UPArZ6krsCd44PZK5zut7f8tQfb7HqVH; sessionid=qszf9bsjr5kxlm9t9avhx8ivogoa4q44"  # For bypassing Cloudflare captcha
 | 
				
			||||||
 | 
					USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0"  # For bypassing Cloudflare captcha
 | 
				
			||||||
 | 
					HTML_VIEWER = False  # Generate HTML viewer
 | 
				
			||||||
 | 
					GENERATE_CBZ = True  # Generate Comic Book Archive
 | 
				
			||||||
 | 
					GENERATE_PDF = False  # Generate PDF file
 | 
				
			||||||
 | 
					THREAD_COUNT = 1  # Thread count for downloading
 | 
				
			||||||
 | 
					TIMEOUT = 30  # Timeout in seconds
 | 
				
			||||||
 | 
					RETRY_COUNT = 3  # Retry times when download fails
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Database setup
 | 
				
			||||||
 | 
					def init_database():
 | 
				
			||||||
 | 
					    conn = sqlite3.connect('nhentai_downloads.db')
 | 
				
			||||||
 | 
					    cursor = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Create downloads table if it doesn't exist
 | 
				
			||||||
 | 
					    cursor.execute('''
 | 
				
			||||||
 | 
					    CREATE TABLE IF NOT EXISTS downloads (
 | 
				
			||||||
 | 
					        id INTEGER PRIMARY KEY,
 | 
				
			||||||
 | 
					        nhentai_id TEXT NOT NULL,
 | 
				
			||||||
 | 
					        status TEXT NOT NULL,
 | 
				
			||||||
 | 
					        message TEXT,
 | 
				
			||||||
 | 
					        added_time TIMESTAMP NOT NULL,
 | 
				
			||||||
 | 
					        updated_time TIMESTAMP NOT NULL
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    ''')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    conn.commit()
 | 
				
			||||||
 | 
					    conn.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def db_add_download(nhentai_id):
 | 
				
			||||||
 | 
					    conn = sqlite3.connect('nhentai_downloads.db')
 | 
				
			||||||
 | 
					    cursor = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    now = datetime.now().isoformat()
 | 
				
			||||||
 | 
					    cursor.execute(
 | 
				
			||||||
 | 
					        "INSERT INTO downloads (nhentai_id, status, added_time, updated_time) VALUES (?, ?, ?, ?)",
 | 
				
			||||||
 | 
					        (nhentai_id, "pending", now, now)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    conn.commit()
 | 
				
			||||||
 | 
					    conn.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def db_update_download_status(nhentai_id, status, message=None):
 | 
				
			||||||
 | 
					    conn = sqlite3.connect('nhentai_downloads.db')
 | 
				
			||||||
 | 
					    cursor = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    now = datetime.now().isoformat()
 | 
				
			||||||
 | 
					    cursor.execute(
 | 
				
			||||||
 | 
					        "UPDATE downloads SET status = ?, message = ?, updated_time = ? WHERE nhentai_id = ?",
 | 
				
			||||||
 | 
					        (status, message, now, nhentai_id)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    conn.commit()
 | 
				
			||||||
 | 
					    conn.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def db_get_all_downloads():
 | 
				
			||||||
 | 
					    conn = sqlite3.connect('nhentai_downloads.db')
 | 
				
			||||||
 | 
					    conn.row_factory = sqlite3.Row  # This enables column access by name
 | 
				
			||||||
 | 
					    cursor = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cursor.execute("SELECT * FROM downloads ORDER BY added_time DESC")
 | 
				
			||||||
 | 
					    downloads = [dict(row) for row in cursor.fetchall()]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    conn.close()
 | 
				
			||||||
 | 
					    return downloads
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def db_get_download_status(nhentai_id):
 | 
				
			||||||
 | 
					    conn = sqlite3.connect('nhentai_downloads.db')
 | 
				
			||||||
 | 
					    conn.row_factory = sqlite3.Row
 | 
				
			||||||
 | 
					    cursor = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cursor.execute("SELECT status, message FROM downloads WHERE nhentai_id = ?", (nhentai_id,))
 | 
				
			||||||
 | 
					    result = cursor.fetchone()
 | 
				
			||||||
 | 
					    conn.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if result:
 | 
				
			||||||
 | 
					        return {'status': result['status'], 'message': result['message']}
 | 
				
			||||||
 | 
					    return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def download_doujinshi(nhentai_id, on_status_change=None):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Download a doujinshi using nhentai CLI.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        nhentai_id: The ID to download
 | 
				
			||||||
 | 
					        on_status_change: Optional callback for status updates
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    # Update status in the database
 | 
				
			||||||
 | 
					    db_update_download_status(nhentai_id, "downloading")
 | 
				
			||||||
 | 
					    if on_status_change:
 | 
				
			||||||
 | 
					        on_status_change(nhentai_id, "downloading")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        # Try to check if nhentai CLI is installed
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            subprocess.run(["nhentai", "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
 | 
				
			||||||
 | 
					        except (subprocess.SubprocessError, FileNotFoundError):
 | 
				
			||||||
 | 
					            error_msg = "nhentai CLI tool is not installed"
 | 
				
			||||||
 | 
					            db_update_download_status(nhentai_id, "error", error_msg)
 | 
				
			||||||
 | 
					            if on_status_change:
 | 
				
			||||||
 | 
					                on_status_change(nhentai_id, "error", error_msg)
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Create download directory if it doesn't exist
 | 
				
			||||||
 | 
					        os.makedirs(DOWNLOAD_DIR, exist_ok=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Build command with all options
 | 
				
			||||||
 | 
					        cmd = ["nhentai"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Add the ID
 | 
				
			||||||
 | 
					        cmd.extend(["--id", str(nhentai_id)])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Add explicit download flag to make sure it downloads
 | 
				
			||||||
 | 
					        cmd.append("--download")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Add output directory
 | 
				
			||||||
 | 
					        cmd.extend(["--output", DOWNLOAD_DIR])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Cleanup
 | 
				
			||||||
 | 
					        cmd.extend(["--move-to-folder"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Add format option
 | 
				
			||||||
 | 
					        cmd.extend(["--format", f"'{OUTPUT_FORMAT}'"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Add thread count
 | 
				
			||||||
 | 
					        if THREAD_COUNT > 0:
 | 
				
			||||||
 | 
					            cmd.extend(["--threads", str(THREAD_COUNT)])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Add timeout
 | 
				
			||||||
 | 
					        if TIMEOUT > 0:
 | 
				
			||||||
 | 
					            cmd.extend(["--timeout", str(TIMEOUT)])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Add retry count
 | 
				
			||||||
 | 
					        if RETRY_COUNT > 0:
 | 
				
			||||||
 | 
					            cmd.extend(["--retry", str(RETRY_COUNT)])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Add exit-on-fail option to prevent incomplete downloads
 | 
				
			||||||
 | 
					        cmd.append("--exit-on-fail")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Add HTML viewer option
 | 
				
			||||||
 | 
					        if HTML_VIEWER:
 | 
				
			||||||
 | 
					            cmd.append("--html")
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            cmd.append("--no-html")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Generate CBZ if selected
 | 
				
			||||||
 | 
					        if GENERATE_CBZ:
 | 
				
			||||||
 | 
					            cmd.append("--cbz")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Generate PDF if selected
 | 
				
			||||||
 | 
					        if GENERATE_PDF:
 | 
				
			||||||
 | 
					            cmd.append("--pdf")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Add cookie if provided
 | 
				
			||||||
 | 
					        if COOKIE:
 | 
				
			||||||
 | 
					            subprocess.run(["nhentai", f"--cookie=\"{COOKIE}\""],
 | 
				
			||||||
 | 
					                stdout=subprocess.PIPE,
 | 
				
			||||||
 | 
					                stderr=subprocess.PIPE)
 | 
				
			||||||
 | 
					            print("Set nhentai cookie")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Add user agent if provided
 | 
				
			||||||
 | 
					        if USER_AGENT:
 | 
				
			||||||
 | 
					            subprocess.run(["nhentai", f"--useragent=\"{USER_AGENT}\""],
 | 
				
			||||||
 | 
					                stdout=subprocess.PIPE,
 | 
				
			||||||
 | 
					                stderr=subprocess.PIPE)
 | 
				
			||||||
 | 
					            print("Set nhentai user agent")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # For debugging: print the command (excluding sensitive info)
 | 
				
			||||||
 | 
					        debug_cmd = list(cmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        print(f"Executing: {' '.join(debug_cmd)}")
 | 
				
			||||||
 | 
					        print(f"Working directory: {os.getcwd()}")
 | 
				
			||||||
 | 
					        print(f"Download directory: {DOWNLOAD_DIR}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        process = subprocess.Popen(
 | 
				
			||||||
 | 
					            cmd,
 | 
				
			||||||
 | 
					            stdout=subprocess.PIPE,
 | 
				
			||||||
 | 
					            stderr=subprocess.PIPE,
 | 
				
			||||||
 | 
					            text=True
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        stdout, stderr = process.communicate()
 | 
				
			||||||
 | 
					        print(f"Full STDOUT: {stdout}")
 | 
				
			||||||
 | 
					        if (stderr):
 | 
				
			||||||
 | 
					            print(f"Full STDERR: {stderr}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if process.returncode == 0:
 | 
				
			||||||
 | 
					            # Success
 | 
				
			||||||
 | 
					            db_update_download_status(nhentai_id, "success", "Download completed")
 | 
				
			||||||
 | 
					            if on_status_change:
 | 
				
			||||||
 | 
					                on_status_change(nhentai_id, "success", "Download completed")
 | 
				
			||||||
 | 
					            print(f"Download successful for ID {nhentai_id}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # Error
 | 
				
			||||||
 | 
					            error_msg = f'Download failed: {stderr}'
 | 
				
			||||||
 | 
					            db_update_download_status(nhentai_id, "error", error_msg)
 | 
				
			||||||
 | 
					            if on_status_change:
 | 
				
			||||||
 | 
					                on_status_change(nhentai_id, "error", error_msg)
 | 
				
			||||||
 | 
					            print(f"Download failed for ID {nhentai_id}")
 | 
				
			||||||
 | 
					            print(f"STDERR: {stderr}")
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        # Error handling
 | 
				
			||||||
 | 
					        error_msg = f'Error: {str(e)}'
 | 
				
			||||||
 | 
					        db_update_download_status(nhentai_id, "error", error_msg)
 | 
				
			||||||
 | 
					        if on_status_change:
 | 
				
			||||||
 | 
					            on_status_change(nhentai_id, "error", error_msg)
 | 
				
			||||||
 | 
					        print(f"Exception during download for ID {nhentai_id}: {str(e)}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def start_download_thread(nhentai_id, on_status_change=None):
 | 
				
			||||||
 | 
					    """Starts a download in a background thread"""
 | 
				
			||||||
 | 
					    threading.Thread(
 | 
				
			||||||
 | 
					        target=download_doujinshi,
 | 
				
			||||||
 | 
					        args=(nhentai_id, on_status_change),
 | 
				
			||||||
 | 
					        daemon=True
 | 
				
			||||||
 | 
					    ).start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_settings():
 | 
				
			||||||
 | 
					    """Return current settings as a dict"""
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        "download_dir": DOWNLOAD_DIR,
 | 
				
			||||||
 | 
					        "output_format": OUTPUT_FORMAT,
 | 
				
			||||||
 | 
					        "cookie": COOKIE,
 | 
				
			||||||
 | 
					        "user_agent": USER_AGENT,
 | 
				
			||||||
 | 
					        "html_viewer": HTML_VIEWER,
 | 
				
			||||||
 | 
					        "generate_cbz": GENERATE_CBZ,
 | 
				
			||||||
 | 
					        "generate_pdf": GENERATE_PDF,
 | 
				
			||||||
 | 
					        "thread_count": THREAD_COUNT,
 | 
				
			||||||
 | 
					        "timeout": TIMEOUT,
 | 
				
			||||||
 | 
					        "retry_count": RETRY_COUNT
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def update_settings(settings):
 | 
				
			||||||
 | 
					    """Update global settings from a dict"""
 | 
				
			||||||
 | 
					    global DOWNLOAD_DIR, OUTPUT_FORMAT, COOKIE, USER_AGENT, HTML_VIEWER
 | 
				
			||||||
 | 
					    global GENERATE_CBZ, GENERATE_PDF, THREAD_COUNT, TIMEOUT, RETRY_COUNT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    DOWNLOAD_DIR = settings.get("download_dir", DOWNLOAD_DIR)
 | 
				
			||||||
 | 
					    OUTPUT_FORMAT = settings.get("output_format", OUTPUT_FORMAT)
 | 
				
			||||||
 | 
					    COOKIE = settings.get("cookie", COOKIE)
 | 
				
			||||||
 | 
					    USER_AGENT = settings.get("user_agent", USER_AGENT)
 | 
				
			||||||
 | 
					    HTML_VIEWER = settings.get("html_viewer", HTML_VIEWER)
 | 
				
			||||||
 | 
					    GENERATE_CBZ = settings.get("generate_cbz", GENERATE_CBZ)
 | 
				
			||||||
 | 
					    GENERATE_PDF = settings.get("generate_pdf", GENERATE_PDF)
 | 
				
			||||||
 | 
					    THREAD_COUNT = settings.get("thread_count", THREAD_COUNT)
 | 
				
			||||||
 | 
					    TIMEOUT = settings.get("timeout", TIMEOUT)
 | 
				
			||||||
 | 
					    RETRY_COUNT = settings.get("retry_count", RETRY_COUNT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Initialize database when module is imported
 | 
				
			||||||
 | 
					init_database()
 | 
				
			||||||
		Reference in New Issue
	
	Block a user