Getting some functionality

This commit is contained in:
Lord Of Nougate 2025-03-17 00:05:20 +01:00
parent bef59fd760
commit 2b089f5a62

View File

@ -1,8 +1,28 @@
from nicegui import ui from nicegui import ui, app
import time import time
import subprocess
import os
import threading
from collections import defaultdict
ui.dark_mode().enable() ui.dark_mode().enable()
# Configuration
DOWNLOAD_DIR = os.path.expanduser("~/Downloads/doujinshi")
DOCKER_IMAGE = "ricterz/nhentai" # Official Docker image from DockerHub
OUTPUT_FORMAT = "[%i]%s" # Default format for folder naming
COOKIE = "" # For bypassing Cloudflare captcha
USER_AGENT = "" # For bypassing Cloudflare captcha
HTML_VIEWER = True # Generate HTML viewer
GENERATE_CBZ = False # Generate Comic Book Archive
GENERATE_PDF = False # Generate PDF file
THREAD_COUNT = 5 # Thread count for downloading
TIMEOUT = 30 # Timeout in seconds
RETRY_COUNT = 3 # Retry times when download fails
# Global dictionary to track download statuses
download_statuses = {}
def add_nhentai_id(): def add_nhentai_id():
id_value = id_input.value id_value = id_input.value
if not id_value: if not id_value:
@ -23,25 +43,254 @@ def add_nhentai_id():
ui.label(f'ID: {id_value}').classes('text-lg ml-2') ui.label(f'ID: {id_value}').classes('text-lg ml-2')
timestamp = ui.label(f'Added: {time.strftime("%H:%M:%S")}').classes('text-sm text-gray-500 ml-auto') timestamp = ui.label(f'Added: {time.strftime("%H:%M:%S")}').classes('text-sm text-gray-500 ml-auto')
# Add a button to mark as success # Add buttons for actions
ui.button('Mark Complete', download_btn = ui.button('Download',
on_click=lambda: update_status(status, id_value, True), on_click=lambda: start_download(id_value, status),
color='green').classes('ml-2').props('size=sm') color='primary').classes('ml-2').props('size=sm')
complete_btn = ui.button('Mark Complete',
on_click=lambda: update_status(status, id_value, True),
color='green').classes('ml-2').props('size=sm')
def update_status(status_icon, id_value, success=True): def update_status(status_icon, id_value, success=True):
if success: if success:
status_icon.name = 'check_circle' status_icon.name = 'check_circle'
status_icon.classes('text-green-500', remove='text-yellow-500') status_icon.classes('text-green-500', remove='text-yellow-500 text-red-500 text-blue-500')
ui.notify(f'ID {id_value} marked as complete', color='positive') ui.notify(f'ID {id_value} marked as complete', color='positive')
else: else:
status_icon.name = 'error' status_icon.name = 'error'
status_icon.classes('text-red-500', remove='text-yellow-500') status_icon.classes('text-red-500', remove='text-yellow-500 text-green-500 text-blue-500')
ui.notify(f'ID {id_value} marked as failed', color='negative') ui.notify(f'ID {id_value} marked as failed', color='negative')
ui.label('nhentai ID Tracker').classes('text-2xl font-bold mb-4') def download_doujinshi(id_value, status_icon):
# This function runs in a background thread
try:
# Create download directory if it doesn't exist
os.makedirs(DOWNLOAD_DIR, exist_ok=True)
# Build Docker command with all options
cmd = [
"docker", "run", "--rm",
"-v", f"{DOWNLOAD_DIR}:/output",
"-v", f"{os.path.expanduser('~/.nhentai')}:/root/.nhentai",
DOCKER_IMAGE
]
# Add the ID
cmd.extend(["--id", str(id_value)])
# Add output directory
cmd.extend(["--output", "/output"])
# Add format option
cmd.extend(["--format", 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:
cmd.extend(["--cookie", COOKIE])
# Add user agent if provided
if USER_AGENT:
cmd.extend(["--useragent", USER_AGENT])
# Save download history
cmd.append("--save-download-history")
# For debugging: print the command (excluding sensitive info)
debug_cmd = list(cmd)
if COOKIE:
cookie_index = debug_cmd.index("--cookie")
if len(debug_cmd) > cookie_index + 1:
debug_cmd[cookie_index + 1] = "***COOKIE***"
if USER_AGENT:
ua_index = debug_cmd.index("--useragent")
if len(debug_cmd) > ua_index + 1:
debug_cmd[ua_index + 1] = "***USER-AGENT***"
print(f"Executing: {' '.join(debug_cmd)}")
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
stdout, stderr = process.communicate()
# Use the global dict to track status
global download_statuses
if process.returncode == 0:
# Success
download_statuses[id_value] = {
'status': 'success',
'message': 'Download completed'
}
print(f"Download successful for ID {id_value}")
print(f"STDOUT: {stdout}")
else:
# Error
download_statuses[id_value] = {
'status': 'error',
'message': f'Download failed: {stderr}'
}
print(f"Download failed for ID {id_value}")
print(f"STDERR: {stderr}")
except Exception as e:
# Error handling
download_statuses[id_value] = {
'status': 'error',
'message': f'Error: {str(e)}'
}
print(f"Exception during download for ID {id_value}: {str(e)}")
def start_download(id_value, status_icon):
# Update status to "downloading"
status_icon.name = 'downloading'
status_icon.classes('text-blue-500', remove='text-yellow-500 text-green-500 text-red-500')
ui.notify(f'Starting download for ID: {id_value}', color='info')
# Initialize download status
global download_statuses
download_statuses[id_value] = {'status': 'downloading'}
# Start download in a background thread
threading.Thread(target=download_doujinshi, args=(id_value, status_icon), daemon=True).start()
# Set up a periodic task to check for download completion
check_download_status(id_value, status_icon)
def check_download_status(id_value, status_icon):
"""Check if the download has completed and update the UI accordingly"""
global download_statuses
if id_value in download_statuses:
result = download_statuses[id_value]
if result.get('status') == 'success':
ui.notify(f'Download completed for ID: {id_value}', color='positive')
update_status(status_icon, id_value, True)
# Remove from tracking to avoid duplicate notifications
del download_statuses[id_value]
elif result.get('status') == 'error':
ui.notify(f'Download failed for ID {id_value}: {result.get("message", "Unknown error")}', color='negative')
update_status(status_icon, id_value, False)
# Remove from tracking to avoid duplicate notifications
del download_statuses[id_value]
else:
# Still processing, check again in a second
ui.timer(1.0, lambda: check_download_status(id_value, status_icon), once=True)
else:
# No result yet, check again in a second
ui.timer(1.0, lambda: check_download_status(id_value, status_icon), once=True)
# Add advanced settings dialog
def show_settings():
with ui.dialog() as dialog, ui.card().classes('w-96'):
ui.label('Download Settings').classes('text-xl font-bold')
global DOWNLOAD_DIR, DOCKER_IMAGE, OUTPUT_FORMAT, COOKIE, USER_AGENT, HTML_VIEWER, GENERATE_CBZ, GENERATE_PDF, THREAD_COUNT, TIMEOUT, RETRY_COUNT
# Basic Settings
ui.label('Basic Settings').classes('text-lg font-semibold mt-4')
download_dir_input = ui.input('Download Directory', value=DOWNLOAD_DIR).classes('w-full')
docker_image_input = ui.input('Docker Image', value=DOCKER_IMAGE).classes('w-full')
output_format_input = ui.input('Output Format', value=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=THREAD_COUNT, min=1, max=20)
timeout_input = ui.number('Timeout (seconds)', value=TIMEOUT, min=5, max=120)
retry_count_input = ui.number('Retry Count', value=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=HTML_VIEWER)
cbz_toggle = ui.switch('Generate CBZ File', value=GENERATE_CBZ)
pdf_toggle = ui.switch('Generate PDF File', value=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=COOKIE,
placeholder='csrftoken=TOKEN; sessionid=ID; cf_clearance=CLOUDFLARE').classes('w-full')
useragent_input = ui.input('User Agent', value=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,
docker_image_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, docker_image, output_format, cookie, useragent,
html_viewer, generate_cbz, generate_pdf, thread_count, timeout, retry_count, dialog):
global DOWNLOAD_DIR, DOCKER_IMAGE, OUTPUT_FORMAT, COOKIE, USER_AGENT, HTML_VIEWER, GENERATE_CBZ, GENERATE_PDF, THREAD_COUNT, TIMEOUT, RETRY_COUNT
DOWNLOAD_DIR = download_dir
DOCKER_IMAGE = docker_image
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
ui.notify('Settings saved', color='positive')
dialog.close()
# UI Layout
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 # Input and button row
with ui.row().classes('w-full items-center'): with ui.row().classes('w-full items-center mt-4'):
id_input = ui.input(label='ID:').classes('mr-2') id_input = ui.input(label='ID:').classes('mr-2')
ui.button('Add ID!', on_click=add_nhentai_id).props('icon=add') ui.button('Add ID!', on_click=add_nhentai_id).props('icon=add')
@ -53,4 +302,9 @@ history_container = ui.column().classes('w-full border rounded-lg p-2')
with history_container: with history_container:
ui.label('Enter an ID and click "Add ID!" to see the history').classes('text-gray-500 italic') 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 ricterz/nhentai Docker image. Need help? Check the GitHub page:')
ui.link('https://github.com/RicterZ/nhentai', 'https://github.com/RicterZ/nhentai').classes('ml-1')
ui.run() ui.run()