mirror of
https://github.com/Yuvi9587/Kemono-Downloader.git
synced 2025-12-29 16:14:44 +00:00
Compare commits
3 Commits
4a93b721e2
...
v7.6.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be03f914ef | ||
|
|
ec9900b90f | ||
|
|
55ebfdb980 |
@@ -134,7 +134,9 @@ class SimpCityDownloadThread(QThread):
|
|||||||
with self.counter_lock: self.total_skip_count += 1
|
with self.counter_lock: self.total_skip_count += 1
|
||||||
return
|
return
|
||||||
self.progress_signal.emit(f" -> Downloading (Image): '{filename}'...")
|
self.progress_signal.emit(f" -> Downloading (Image): '{filename}'...")
|
||||||
response = session.get(job['url'], stream=True, timeout=90, headers={'Referer': self.start_url})
|
# --- START MODIFICATION ---
|
||||||
|
response = session.get(job['url'], stream=True, timeout=180, headers={'Referer': self.start_url})
|
||||||
|
# --- END MODIFICATION ---
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
with open(filepath, 'wb') as f:
|
with open(filepath, 'wb') as f:
|
||||||
for chunk in response.iter_content(chunk_size=8192):
|
for chunk in response.iter_content(chunk_size=8192):
|
||||||
@@ -227,7 +229,9 @@ class SimpCityDownloadThread(QThread):
|
|||||||
else:
|
else:
|
||||||
self.progress_signal.emit(f" -> Downloading: '{filename}'...")
|
self.progress_signal.emit(f" -> Downloading: '{filename}'...")
|
||||||
headers = file_data.get('headers', {'Referer': source_url})
|
headers = file_data.get('headers', {'Referer': source_url})
|
||||||
response = session.get(file_data.get('url'), stream=True, timeout=90, headers=headers)
|
# --- START MODIFICATION ---
|
||||||
|
response = session.get(file_data.get('url'), stream=True, timeout=180, headers=headers)
|
||||||
|
# --- END MODIFICATION ---
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
with open(filepath, 'wb') as f:
|
with open(filepath, 'wb') as f:
|
||||||
for chunk in response.iter_content(chunk_size=8192):
|
for chunk in response.iter_content(chunk_size=8192):
|
||||||
@@ -298,16 +302,30 @@ class SimpCityDownloadThread(QThread):
|
|||||||
try:
|
try:
|
||||||
page_title, jobs_on_page, final_url = fetch_single_simpcity_page(page_url, self._log_interceptor, cookies=self.cookies)
|
page_title, jobs_on_page, final_url = fetch_single_simpcity_page(page_url, self._log_interceptor, cookies=self.cookies)
|
||||||
|
|
||||||
|
# --- START: MODIFIED REDIRECT LOGIC ---
|
||||||
if final_url != page_url:
|
if final_url != page_url:
|
||||||
self.progress_signal.emit(f" -> Redirect detected from {page_url} to {final_url}")
|
self.progress_signal.emit(f" -> Redirect detected from {page_url} to {final_url}")
|
||||||
try:
|
try:
|
||||||
req_page_match = re.search(r'/page-(\d+)', page_url)
|
req_page_match = re.search(r'/page-(\d+)', page_url)
|
||||||
final_page_match = re.search(r'/page-(\d+)', final_url)
|
final_page_match = re.search(r'/page-(\d+)', final_url)
|
||||||
if req_page_match and final_page_match and int(final_page_match.group(1)) < int(req_page_match.group(1)):
|
|
||||||
self.progress_signal.emit(" -> Redirected to an earlier page. Reached end of thread.")
|
if req_page_match:
|
||||||
end_of_thread = True
|
req_page_num = int(req_page_match.group(1))
|
||||||
|
|
||||||
|
# Scenario 1: Redirect to an earlier page (e.g., page-11 -> page-10)
|
||||||
|
if final_page_match and int(final_page_match.group(1)) < req_page_num:
|
||||||
|
self.progress_signal.emit(f" -> Redirected to an earlier page ({final_page_match.group(0)}). Reached end of thread.")
|
||||||
|
end_of_thread = True
|
||||||
|
|
||||||
|
# Scenario 2: Redirect to base URL (e.g., page-11 -> /)
|
||||||
|
# We check req_page_num > 1 because page-1 often redirects to base URL, which is normal.
|
||||||
|
elif not final_page_match and req_page_num > 1:
|
||||||
|
self.progress_signal.emit(f" -> Redirected to base thread URL. Reached end of thread.")
|
||||||
|
end_of_thread = True
|
||||||
|
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
pass
|
pass # Ignore parsing errors
|
||||||
|
# --- END: MODIFIED REDIRECT LOGIC ---
|
||||||
|
|
||||||
if end_of_thread:
|
if end_of_thread:
|
||||||
page_fetch_successful = True; break
|
page_fetch_successful = True; break
|
||||||
@@ -316,25 +334,40 @@ class SimpCityDownloadThread(QThread):
|
|||||||
self.progress_signal.emit(f" -> Page {page_counter} is invalid or has no title. Reached end of thread.")
|
self.progress_signal.emit(f" -> Page {page_counter} is invalid or has no title. Reached end of thread.")
|
||||||
end_of_thread = True
|
end_of_thread = True
|
||||||
elif not jobs_on_page:
|
elif not jobs_on_page:
|
||||||
|
self.progress_signal.emit(f" -> Page {page_counter} has no content. Reached end of thread.")
|
||||||
end_of_thread = True
|
end_of_thread = True
|
||||||
else:
|
else:
|
||||||
new_jobs = [job for job in jobs_on_page if job.get('url') not in self.processed_job_urls]
|
new_jobs = [job for job in jobs_on_page if job.get('url') not in self.processed_job_urls]
|
||||||
if not new_jobs and page_counter > 1:
|
if not new_jobs and page_counter > 1:
|
||||||
|
self.progress_signal.emit(f" -> Page {page_counter} contains no new content. Reached end of thread.")
|
||||||
end_of_thread = True
|
end_of_thread = True
|
||||||
else:
|
else:
|
||||||
enriched_jobs = self._get_enriched_jobs(new_jobs)
|
enriched_jobs = self._get_enriched_jobs(new_jobs)
|
||||||
for job in enriched_jobs:
|
if not enriched_jobs and not new_jobs:
|
||||||
self.processed_job_urls.add(job.get('url'))
|
# This can happen if all new_jobs were e.g. pixeldrain and it's disabled
|
||||||
if job['type'] == 'image': self.image_queue.put(job)
|
self.progress_signal.emit(f" -> Page {page_counter} content was filtered out. Reached end of thread.")
|
||||||
else: self.service_queue.put(job)
|
end_of_thread = True
|
||||||
|
else:
|
||||||
|
for job in enriched_jobs:
|
||||||
|
self.processed_job_urls.add(job.get('url'))
|
||||||
|
if job['type'] == 'image': self.image_queue.put(job)
|
||||||
|
else: self.service_queue.put(job)
|
||||||
page_fetch_successful = True; break
|
page_fetch_successful = True; break
|
||||||
except requests.exceptions.HTTPError as e:
|
except requests.exceptions.HTTPError as e:
|
||||||
if e.response.status_code in [403, 404]: end_of_thread = True; break
|
if e.response.status_code in [403, 404]:
|
||||||
elif e.response.status_code == 429: time.sleep(5 * (retries + 2)); retries += 1
|
self.progress_signal.emit(f" -> Page {page_counter} returned {e.response.status_code}. Reached end of thread.")
|
||||||
else: end_of_thread = True; break
|
end_of_thread = True; break
|
||||||
|
elif e.response.status_code == 429:
|
||||||
|
self.progress_signal.emit(f" -> Rate limited (429). Waiting...")
|
||||||
|
time.sleep(5 * (retries + 2)); retries += 1
|
||||||
|
else:
|
||||||
|
self.progress_signal.emit(f" -> HTTP Error {e.response.status_code} on page {page_counter}. Stopping crawl.")
|
||||||
|
end_of_thread = True; break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.progress_signal.emit(f" Stopping crawl due to error on page {page_counter}: {e}"); end_of_thread = True; break
|
self.progress_signal.emit(f" Stopping crawl due to error on page {page_counter}: {e}"); end_of_thread = True; break
|
||||||
if not page_fetch_successful and not end_of_thread: end_of_thread = True
|
if not page_fetch_successful and not end_of_thread:
|
||||||
|
self.progress_signal.emit(f" -> Failed to fetch page {page_counter} after {MAX_RETRIES} attempts. Stopping crawl.")
|
||||||
|
end_of_thread = True
|
||||||
if not end_of_thread: page_counter += 1
|
if not end_of_thread: page_counter += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.progress_signal.emit(f"❌ A critical error occurred during the main fetch phase: {e}")
|
self.progress_signal.emit(f"❌ A critical error occurred during the main fetch phase: {e}")
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ class EmptyPopupDialog (QDialog ):
|
|||||||
SCOPE_CREATORS ="Creators"
|
SCOPE_CREATORS ="Creators"
|
||||||
|
|
||||||
|
|
||||||
def __init__ (self ,app_base_dir ,parent_app_ref ,parent =None ):
|
def __init__ (self ,user_data_path ,parent_app_ref ,parent =None ):
|
||||||
super ().__init__ (parent )
|
super ().__init__ (parent )
|
||||||
self.parent_app = parent_app_ref
|
self.parent_app = parent_app_ref
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ class EmptyPopupDialog (QDialog ):
|
|||||||
|
|
||||||
self.setMinimumSize(int(400 * scale_factor), int(300 * scale_factor))
|
self.setMinimumSize(int(400 * scale_factor), int(300 * scale_factor))
|
||||||
self.current_scope_mode = self.SCOPE_CREATORS
|
self.current_scope_mode = self.SCOPE_CREATORS
|
||||||
self .app_base_dir =app_base_dir
|
self.user_data_path = user_data_path
|
||||||
|
|
||||||
app_icon =get_app_icon_object ()
|
app_icon =get_app_icon_object ()
|
||||||
if app_icon and not app_icon .isNull ():
|
if app_icon and not app_icon .isNull ():
|
||||||
@@ -336,7 +336,7 @@ class EmptyPopupDialog (QDialog ):
|
|||||||
"""
|
"""
|
||||||
# --- NEW BEHAVIOR ---
|
# --- NEW BEHAVIOR ---
|
||||||
# Pass the app_base_dir and a reference to the main app (for translations/theme)
|
# Pass the app_base_dir and a reference to the main app (for translations/theme)
|
||||||
dialog = UpdateCheckDialog(self.app_base_dir, self.parent_app, self)
|
dialog = UpdateCheckDialog(self.user_data_path, self.parent_app, self)
|
||||||
|
|
||||||
if dialog.exec_() == QDialog.Accepted:
|
if dialog.exec_() == QDialog.Accepted:
|
||||||
# --- MODIFIED: Get a list of profiles now ---
|
# --- MODIFIED: Get a list of profiles now ---
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ class UpdateCheckDialog(QDialog):
|
|||||||
and allows the user to select multiple to check for updates.
|
and allows the user to select multiple to check for updates.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, app_base_dir, parent_app_ref, parent=None):
|
def __init__(self, user_data_path, parent_app_ref, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.parent_app = parent_app_ref
|
self.parent_app = parent_app_ref
|
||||||
self.app_base_dir = app_base_dir
|
self.user_data_path = user_data_path
|
||||||
self.selected_profiles_list = [] # Will store a list of {'name': ..., 'data': ...}
|
self.selected_profiles_list = [] # Will store a list of {'name': ..., 'data': ...}
|
||||||
|
|
||||||
self._init_ui()
|
self._init_ui()
|
||||||
@@ -100,7 +100,7 @@ class UpdateCheckDialog(QDialog):
|
|||||||
|
|
||||||
def _load_profiles(self):
|
def _load_profiles(self):
|
||||||
"""Loads all .json files from the creator_profiles directory as checkable items."""
|
"""Loads all .json files from the creator_profiles directory as checkable items."""
|
||||||
appdata_dir = os.path.join(self.app_base_dir, "appdata")
|
appdata_dir = self.user_data_path
|
||||||
profiles_dir = os.path.join(appdata_dir, "creator_profiles")
|
profiles_dir = os.path.join(appdata_dir, "creator_profiles")
|
||||||
|
|
||||||
if not os.path.isdir(profiles_dir):
|
if not os.path.isdir(profiles_dir):
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ class DownloaderApp (QWidget ):
|
|||||||
self.download_location_label_widget = None
|
self.download_location_label_widget = None
|
||||||
self.remove_from_filename_label_widget = None
|
self.remove_from_filename_label_widget = None
|
||||||
self.skip_words_label_widget = None
|
self.skip_words_label_widget = None
|
||||||
self.setWindowTitle("Kemono Downloader v7.6.0")
|
self.setWindowTitle("Kemono Downloader v7.6.1")
|
||||||
setup_ui(self)
|
setup_ui(self)
|
||||||
self._connect_signals()
|
self._connect_signals()
|
||||||
if hasattr(self, 'character_input'):
|
if hasattr(self, 'character_input'):
|
||||||
@@ -3888,30 +3888,6 @@ class DownloaderApp (QWidget ):
|
|||||||
num_threads_from_gui = MAX_THREADS
|
num_threads_from_gui = MAX_THREADS
|
||||||
self.thread_count_input.setText(str(MAX_THREADS))
|
self.thread_count_input.setText(str(MAX_THREADS))
|
||||||
self.log_signal.emit(f"⚠️ User attempted {num_threads_from_gui} threads, capped to {MAX_THREADS}.")
|
self.log_signal.emit(f"⚠️ User attempted {num_threads_from_gui} threads, capped to {MAX_THREADS}.")
|
||||||
if SOFT_WARNING_THREAD_THRESHOLD < num_threads_from_gui <= MAX_THREADS:
|
|
||||||
soft_warning_msg_box = QMessageBox(self)
|
|
||||||
soft_warning_msg_box.setIcon(QMessageBox.Question)
|
|
||||||
soft_warning_msg_box.setWindowTitle("Thread Count Advisory")
|
|
||||||
soft_warning_msg_box.setText(
|
|
||||||
f"You've set the thread count to {num_threads_from_gui}.\n\n"
|
|
||||||
"While this is within the allowed limit, using a high number of threads (typically above 40-50) can sometimes lead to:\n"
|
|
||||||
" - Increased errors or failed file downloads.\n"
|
|
||||||
" - Connection issues with the server.\n"
|
|
||||||
" - Higher system resource usage.\n\n"
|
|
||||||
"For most users and connections, 10-30 threads provide a good balance.\n\n"
|
|
||||||
f"Do you want to proceed with {num_threads_from_gui} threads, or would you like to change the value?"
|
|
||||||
)
|
|
||||||
proceed_button = soft_warning_msg_box.addButton("Proceed Anyway", QMessageBox.AcceptRole)
|
|
||||||
change_button = soft_warning_msg_box.addButton("Change Thread Value", QMessageBox.RejectRole)
|
|
||||||
soft_warning_msg_box.setDefaultButton(proceed_button)
|
|
||||||
soft_warning_msg_box.setEscapeButton(change_button)
|
|
||||||
soft_warning_msg_box.exec_()
|
|
||||||
|
|
||||||
if soft_warning_msg_box.clickedButton() == change_button:
|
|
||||||
self.log_signal.emit(f"ℹ️ User opted to change thread count from {num_threads_from_gui} after advisory.")
|
|
||||||
self.thread_count_input.setFocus()
|
|
||||||
self.thread_count_input.selectAll()
|
|
||||||
return False
|
|
||||||
|
|
||||||
raw_skip_words_text = self.skip_words_input.text().strip()
|
raw_skip_words_text = self.skip_words_input.text().strip()
|
||||||
skip_words_parts = [part.strip() for part in raw_skip_words_text.split(',') if part.strip()]
|
skip_words_parts = [part.strip() for part in raw_skip_words_text.split(',') if part.strip()]
|
||||||
@@ -4018,26 +3994,6 @@ class DownloaderApp (QWidget ):
|
|||||||
if end_page is not None and end_page <= 0: raise ValueError("End page must be positive.")
|
if end_page is not None and end_page <= 0: raise ValueError("End page must be positive.")
|
||||||
if start_page and end_page and start_page > end_page: raise ValueError("Start page cannot be greater than end page.")
|
if start_page and end_page and start_page > end_page: raise ValueError("Start page cannot be greater than end page.")
|
||||||
|
|
||||||
if manga_mode and start_page and end_page:
|
|
||||||
msg_box = QMessageBox(self)
|
|
||||||
msg_box.setIcon(QMessageBox.Warning)
|
|
||||||
msg_box.setWindowTitle("Renaming Mode & Page Range Warning")
|
|
||||||
msg_box.setText(
|
|
||||||
"You have enabled <b>Renaming Mode</b> with a sequential naming style (<b>Date Based</b> or <b>Title + G.Num</b>) and also specified a <b>Page Range</b>.\n\n"
|
|
||||||
"These modes rely on processing all posts from the beginning to create a correct sequence. "
|
|
||||||
"Using a page range may result in an incomplete or incorrectly ordered download.\n\n"
|
|
||||||
"It is recommended to use these styles without a page range.\n\n"
|
|
||||||
"Do you want to proceed anyway?"
|
|
||||||
)
|
|
||||||
proceed_button = msg_box.addButton("Proceed Anyway", QMessageBox.AcceptRole)
|
|
||||||
cancel_button = msg_box.addButton("Cancel Download", QMessageBox.RejectRole)
|
|
||||||
msg_box.setDefaultButton(proceed_button)
|
|
||||||
msg_box.setEscapeButton(cancel_button)
|
|
||||||
msg_box.exec_()
|
|
||||||
|
|
||||||
if msg_box.clickedButton() == cancel_button:
|
|
||||||
self.log_signal.emit("❌ Download cancelled by user due to Renaming Mode & Page Range warning.")
|
|
||||||
return False
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
QMessageBox.critical(self, "Page Range Error", f"Invalid page range: {e}")
|
QMessageBox.critical(self, "Page Range Error", f"Invalid page range: {e}")
|
||||||
return False
|
return False
|
||||||
@@ -6617,7 +6573,7 @@ class DownloaderApp (QWidget ):
|
|||||||
self._tr("restore_pending_message_creator_selection",
|
self._tr("restore_pending_message_creator_selection",
|
||||||
"Please 'Restore Download' or 'Discard Session' before selecting new creators."))
|
"Please 'Restore Download' or 'Discard Session' before selecting new creators."))
|
||||||
return
|
return
|
||||||
dialog = EmptyPopupDialog(self.app_base_dir, self)
|
dialog = EmptyPopupDialog(self.user_data_path, self)
|
||||||
if dialog.exec_() == QDialog.Accepted:
|
if dialog.exec_() == QDialog.Accepted:
|
||||||
# --- NEW BATCH UPDATE LOGIC ---
|
# --- NEW BATCH UPDATE LOGIC ---
|
||||||
if hasattr(dialog, 'update_profiles_list') and dialog.update_profiles_list:
|
if hasattr(dialog, 'update_profiles_list') and dialog.update_profiles_list:
|
||||||
@@ -6819,26 +6775,6 @@ class DownloaderApp (QWidget ):
|
|||||||
char_filter_is_empty = not self.character_input.text().strip()
|
char_filter_is_empty = not self.character_input.text().strip()
|
||||||
extract_links_only = (self.radio_only_links and self.radio_only_links.isChecked())
|
extract_links_only = (self.radio_only_links and self.radio_only_links.isChecked())
|
||||||
|
|
||||||
if manga_mode_is_checked and char_filter_is_empty and not extract_links_only:
|
|
||||||
msg_box = QMessageBox(self)
|
|
||||||
msg_box.setIcon(QMessageBox.Warning)
|
|
||||||
msg_box.setWindowTitle("Renaming Mode Filter Warning")
|
|
||||||
msg_box.setText(
|
|
||||||
"Renaming Mode is enabled, but 'Filter by Character(s)' is empty.\n\n"
|
|
||||||
"This is a one-time warning for this entire batch of downloads.\n\n"
|
|
||||||
"Proceeding without a filter may result in generic filenames and folders.\n\n"
|
|
||||||
"Proceed with the entire batch?"
|
|
||||||
)
|
|
||||||
proceed_button = msg_box.addButton("Proceed Anyway", QMessageBox.AcceptRole)
|
|
||||||
cancel_button = msg_box.addButton("Cancel Entire Batch", QMessageBox.RejectRole)
|
|
||||||
msg_box.exec_()
|
|
||||||
if msg_box.clickedButton() == cancel_button:
|
|
||||||
self.log_signal.emit("❌ Entire favorite queue cancelled by user at Renaming Mode warning.")
|
|
||||||
self.favorite_download_queue.clear()
|
|
||||||
self.is_processing_favorites_queue = False
|
|
||||||
self.set_ui_enabled(True)
|
|
||||||
return # Stop processing the queue
|
|
||||||
|
|
||||||
if self ._is_download_active ():
|
if self ._is_download_active ():
|
||||||
self .log_signal .emit ("ℹ️ Waiting for current download to finish before starting next favorite.")
|
self .log_signal .emit ("ℹ️ Waiting for current download to finish before starting next favorite.")
|
||||||
return
|
return
|
||||||
|
|||||||
111
structure.txt
Normal file
111
structure.txt
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
├── assets/
|
||||||
|
│ ├── Kemono.ico
|
||||||
|
│ ├── Kemono.png
|
||||||
|
│ ├── Ko-fi.png
|
||||||
|
│ ├── buymeacoffee.png
|
||||||
|
│ ├── discord.png
|
||||||
|
│ ├── github.png
|
||||||
|
│ ├── instagram.png
|
||||||
|
│ └── patreon.png
|
||||||
|
├── data/
|
||||||
|
│ ├── creators.json
|
||||||
|
│ └── dejavu-sans/
|
||||||
|
│ ├── DejaVu Fonts License.txt
|
||||||
|
│ ├── DejaVuSans-Bold.ttf
|
||||||
|
│ ├── DejaVuSans-BoldOblique.ttf
|
||||||
|
│ ├── DejaVuSans-ExtraLight.ttf
|
||||||
|
│ ├── DejaVuSans-Oblique.ttf
|
||||||
|
│ ├── DejaVuSans.ttf
|
||||||
|
│ ├── DejaVuSansCondensed-Bold.ttf
|
||||||
|
│ ├── DejaVuSansCondensed-BoldOblique.ttf
|
||||||
|
│ ├── DejaVuSansCondensed-Oblique.ttf
|
||||||
|
│ └── DejaVuSansCondensed.ttf
|
||||||
|
├── directory_tree.txt
|
||||||
|
├── main.py
|
||||||
|
├── src/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── config/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ └── constants.py
|
||||||
|
│ ├── core/
|
||||||
|
│ │ ├── Hentai2read_client.py
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── allcomic_client.py
|
||||||
|
│ │ ├── api_client.py
|
||||||
|
│ │ ├── booru_client.py
|
||||||
|
│ │ ├── bunkr_client.py
|
||||||
|
│ │ ├── discord_client.py
|
||||||
|
│ │ ├── erome_client.py
|
||||||
|
│ │ ├── fap_nation_client.py
|
||||||
|
│ │ ├── manager.py
|
||||||
|
│ │ ├── mangadex_client.py
|
||||||
|
│ │ ├── nhentai_client.py
|
||||||
|
│ │ ├── pixeldrain_client.py
|
||||||
|
│ │ ├── rule34video_client.py
|
||||||
|
│ │ ├── saint2_client.py
|
||||||
|
│ │ ├── simpcity_client.py
|
||||||
|
│ │ ├── toonily_client.py
|
||||||
|
│ │ └── workers.py
|
||||||
|
│ ├── i18n/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ └── translator.py
|
||||||
|
│ ├── services/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── drive_downloader.py
|
||||||
|
│ │ ├── multipart_downloader.py
|
||||||
|
│ │ └── updater.py
|
||||||
|
│ ├── ui/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── assets.py
|
||||||
|
│ │ ├── classes/
|
||||||
|
│ │ │ ├── allcomic_downloader_thread.py
|
||||||
|
│ │ │ ├── booru_downloader_thread.py
|
||||||
|
│ │ │ ├── bunkr_downloader_thread.py
|
||||||
|
│ │ │ ├── discord_downloader_thread.py
|
||||||
|
│ │ │ ├── downloader_factory.py
|
||||||
|
│ │ │ ├── drive_downloader_thread.py
|
||||||
|
│ │ │ ├── erome_downloader_thread.py
|
||||||
|
│ │ │ ├── external_link_downloader_thread.py
|
||||||
|
│ │ │ ├── fap_nation_downloader_thread.py
|
||||||
|
│ │ │ ├── hentai2read_downloader_thread.py
|
||||||
|
│ │ │ ├── kemono_discord_downloader_thread.py
|
||||||
|
│ │ │ ├── mangadex_downloader_thread.py
|
||||||
|
│ │ │ ├── nhentai_downloader_thread.py
|
||||||
|
│ │ │ ├── pixeldrain_downloader_thread.py
|
||||||
|
│ │ │ ├── rule34video_downloader_thread.py
|
||||||
|
│ │ │ ├── saint2_downloader_thread.py
|
||||||
|
│ │ │ ├── simp_city_downloader_thread.py
|
||||||
|
│ │ │ └── toonily_downloader_thread.py
|
||||||
|
│ │ ├── dialogs/
|
||||||
|
│ │ │ ├── ConfirmAddAllDialog.py
|
||||||
|
│ │ │ ├── CookieHelpDialog.py
|
||||||
|
│ │ │ ├── CustomFilenameDialog.py
|
||||||
|
│ │ │ ├── DownloadExtractedLinksDialog.py
|
||||||
|
│ │ │ ├── DownloadHistoryDialog.py
|
||||||
|
│ │ │ ├── EmptyPopupDialog.py
|
||||||
|
│ │ │ ├── ErrorFilesDialog.py
|
||||||
|
│ │ │ ├── ExportLinksDialog.py
|
||||||
|
│ │ │ ├── ExportOptionsDialog.py
|
||||||
|
│ │ │ ├── FavoriteArtistsDialog.py
|
||||||
|
│ │ │ ├── FavoritePostsDialog.py
|
||||||
|
│ │ │ ├── FutureSettingsDialog.py
|
||||||
|
│ │ │ ├── HelpGuideDialog.py
|
||||||
|
│ │ │ ├── KeepDuplicatesDialog.py
|
||||||
|
│ │ │ ├── KnownNamesFilterDialog.py
|
||||||
|
│ │ │ ├── MoreOptionsDialog.py
|
||||||
|
│ │ │ ├── MultipartScopeDialog.py
|
||||||
|
│ │ │ ├── SinglePDF.py
|
||||||
|
│ │ │ ├── SupportDialog.py
|
||||||
|
│ │ │ ├── TourDialog.py
|
||||||
|
│ │ │ ├── __init__.py
|
||||||
|
│ │ │ └── discord_pdf_generator.py
|
||||||
|
│ │ └── main_window.py
|
||||||
|
│ └── utils/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── command.py
|
||||||
|
│ ├── file_utils.py
|
||||||
|
│ ├── network_utils.py
|
||||||
|
│ ├── resolution.py
|
||||||
|
│ └── text_utils.py
|
||||||
|
├── structure.txt
|
||||||
|
└── yt-dlp.exe
|
||||||
Reference in New Issue
Block a user