From e7a6a91542c6293ac163d184d5dfaae82a4e1c10 Mon Sep 17 00:00:00 2001 From: Yuvi9587 <114073886+Yuvi9587@users.noreply.github.com> Date: Wed, 30 Jul 2025 19:30:50 -0700 Subject: [PATCH] commit --- src/core/workers.py | 48 ++++++++++-- src/ui/dialogs/FavoriteArtistsDialog.py | 100 +++++++++++++++++------- src/ui/dialogs/FavoritePostsDialog.py | 81 ++++++++++++------- src/ui/dialogs/multipart_dialog.py | 81 +++++++++++++++++++ src/ui/main_window.py | 88 ++++++++++++--------- 5 files changed, 297 insertions(+), 101 deletions(-) create mode 100644 src/ui/dialogs/multipart_dialog.py diff --git a/src/core/workers.py b/src/core/workers.py index 585837e..c52e0e9 100644 --- a/src/core/workers.py +++ b/src/core/workers.py @@ -410,6 +410,39 @@ class PostProcessorWorker: unique_id_for_part_file = uuid.uuid4().hex[:8] unique_part_file_stem_on_disk = f"{temp_file_base_for_unique_part}_{unique_id_for_part_file}" max_retries = 3 + if not self.keep_in_post_duplicates: + final_save_path_check = os.path.join(target_folder_path, filename_to_save_in_main_path) + if os.path.exists(final_save_path_check): + try: + # Use a HEAD request to get the expected size without downloading the body + with requests.head(file_url, headers=headers, timeout=15, cookies=cookies_to_use_for_file, allow_redirects=True) as head_response: + head_response.raise_for_status() + expected_size = int(head_response.headers.get('Content-Length', -1)) + + actual_size = os.path.getsize(final_save_path_check) + + if expected_size != -1 and actual_size == expected_size: + self.logger(f" -> Skip (File Exists & Complete): '{filename_to_save_in_main_path}' is already on disk with the correct size.") + + # We still need to add its hash to the session to prevent duplicates in other modes + # This is a quick hash calculation for the already existing file + try: + md5_hasher = hashlib.md5() + with open(final_save_path_check, 'rb') as f_verify: + for chunk in iter(lambda: f_verify.read(8192), b""): + md5_hasher.update(chunk) + + with self.downloaded_hash_counts_lock: + self.downloaded_hash_counts[md5_hasher.hexdigest()] += 1 + except Exception as hash_exc: + self.logger(f" ⚠️ Could not hash existing file '{filename_to_save_in_main_path}' for session: {hash_exc}") + + return 0, 1, filename_to_save_in_main_path, was_original_name_kept_flag, FILE_DOWNLOAD_STATUS_SKIPPED, None + else: + self.logger(f" ⚠️ File '{filename_to_save_in_main_path}' exists but is incomplete (Expected: {expected_size}, Actual: {actual_size}). Re-downloading.") + + except requests.RequestException as e: + self.logger(f" ⚠️ Could not verify size of existing file '{filename_to_save_in_main_path}': {e}. Proceeding with download.") retry_delay = 5 downloaded_size_bytes = 0 calculated_file_hash = None @@ -741,8 +774,11 @@ class PostProcessorWorker: history_data_for_this_post = None parsed_api_url = urlparse(self.api_url_input) - referer_url = f"https://{parsed_api_url.netloc}/" - headers = {'User-Agent': 'Mozilla/5.0', 'Referer': referer_url, 'Accept': '*/*'} + post_data = self.post + post_id = post_data.get('id', 'unknown_id') + + post_page_url = f"https://{parsed_api_url.netloc}/{self.service}/user/{self.user_id}/post/{post_id}" + headers = {'User-Agent': 'Mozilla/5.0', 'Referer': post_page_url, 'Accept': '*/*'} link_pattern = re.compile(r"""]*>(.*?)""", re.IGNORECASE | re.DOTALL) post_data = self.post post_title = post_data.get('title', '') or 'untitled_post' @@ -802,7 +838,7 @@ class PostProcessorWorker: all_files_from_post_api_for_char_check = [] api_file_domain_for_char_check = urlparse(self.api_url_input).netloc if not api_file_domain_for_char_check or not any(d in api_file_domain_for_char_check.lower() for d in ['kemono.su', 'kemono.party', 'kemono.cr', 'coomer.su', 'coomer.party', 'coomer.st']): - api_file_domain_for_char_check = "kemono.su" if "kemono" in self.service.lower() else "coomer.st" + api_file_domain_for_char_check = "kemono.cr" if "kemono" in self.service.lower() else "coomer.st" if post_main_file_info and isinstance(post_main_file_info, dict) and post_main_file_info.get('path'): original_api_name = post_main_file_info.get('name') or os.path.basename(post_main_file_info['path'].lstrip('/')) if original_api_name: @@ -845,9 +881,9 @@ class PostProcessorWorker: try: parsed_input_url_for_comments = urlparse(self.api_url_input) api_domain_for_comments = parsed_input_url_for_comments.netloc - if not any(d in api_domain_for_comments.lower() for d in ['kemono.su', 'kemono.party', 'coomer.su', 'coomer.party']): + if not any(d in api_domain_for_comments.lower() for d in ['kemono.su', 'kemono.party', 'kemono.cr', 'coomer.su', 'coomer.party', 'coomer.st']): self.logger(f"⚠️ Unrecognized domain '{api_domain_for_comments}' for comment API. Defaulting based on service.") - api_domain_for_comments = "kemono.su" if "kemono" in self.service.lower() else "coomer.party" + api_domain_for_comments = "kemono.cr" if "kemono" in self.service.lower() else "coomer.st" comments_data = fetch_post_comments( api_domain_for_comments, self.service, self.user_id, post_id, headers, self.logger, self.cancellation_event, self.pause_event, @@ -1332,7 +1368,7 @@ class PostProcessorWorker: all_files_from_post_api = [] api_file_domain = urlparse(self.api_url_input).netloc if not api_file_domain or not any(d in api_file_domain.lower() for d in ['kemono.su', 'kemono.party', 'kemono.cr', 'coomer.su', 'coomer.party', 'coomer.st']): - api_file_domain = "kemono.su" if "kemono" in self.service.lower() else "coomer.st" + api_file_domain = "kemono.cr" if "kemono" in self.service.lower() else "coomer.st" if post_main_file_info and isinstance(post_main_file_info, dict) and post_main_file_info.get('path'): file_path = post_main_file_info['path'].lstrip('/') original_api_name = post_main_file_info.get('name') or os.path.basename(file_path) diff --git a/src/ui/dialogs/FavoriteArtistsDialog.py b/src/ui/dialogs/FavoriteArtistsDialog.py index 4261281..c06e147 100644 --- a/src/ui/dialogs/FavoriteArtistsDialog.py +++ b/src/ui/dialogs/FavoriteArtistsDialog.py @@ -37,13 +37,13 @@ class FavoriteArtistsDialog (QDialog ): self ._init_ui () self ._fetch_favorite_artists () - def _get_domain_for_service (self ,service_name ): - service_lower =service_name .lower () - coomer_primary_services ={'onlyfans','fansly','manyvids','candfans'} - if service_lower in coomer_primary_services : - return "coomer.su" - else : - return "kemono.su" + def _get_domain_for_service(self, service_name): + service_lower = service_name.lower() + coomer_primary_services = {'onlyfans', 'fansly', 'manyvids', 'candfans'} + if service_lower in coomer_primary_services: + return "coomer.st" # Use the new domain + else: + return "kemono.cr" # Use the new domain def _tr (self ,key ,default_text =""): """Helper to get translation based on current app language.""" @@ -128,9 +128,29 @@ class FavoriteArtistsDialog (QDialog ): def _fetch_favorite_artists (self ): if self.cookies_config['use_cookie']: - # Check if we can load cookies for at least one of the services. - kemono_cookies = prepare_cookies_for_request(True, self.cookies_config['cookie_text'], self.cookies_config['selected_cookie_file'], self.cookies_config['app_base_dir'], self._logger, target_domain="kemono.su") - coomer_cookies = prepare_cookies_for_request(True, self.cookies_config['cookie_text'], self.cookies_config['selected_cookie_file'], self.cookies_config['app_base_dir'], self._logger, target_domain="coomer.su") + # --- Kemono Check with Fallback --- + kemono_cookies = prepare_cookies_for_request( + True, self.cookies_config['cookie_text'], self.cookies_config['selected_cookie_file'], + self.cookies_config['app_base_dir'], self._logger, target_domain="kemono.cr" + ) + if not kemono_cookies: + self._logger("No cookies for kemono.cr, trying fallback kemono.su...") + kemono_cookies = prepare_cookies_for_request( + True, self.cookies_config['cookie_text'], self.cookies_config['selected_cookie_file'], + self.cookies_config['app_base_dir'], self._logger, target_domain="kemono.su" + ) + + # --- Coomer Check with Fallback --- + coomer_cookies = prepare_cookies_for_request( + True, self.cookies_config['cookie_text'], self.cookies_config['selected_cookie_file'], + self.cookies_config['app_base_dir'], self._logger, target_domain="coomer.st" + ) + if not coomer_cookies: + self._logger("No cookies for coomer.st, trying fallback coomer.su...") + coomer_cookies = prepare_cookies_for_request( + True, self.cookies_config['cookie_text'], self.cookies_config['selected_cookie_file'], + self.cookies_config['app_base_dir'], self._logger, target_domain="coomer.su" + ) if not kemono_cookies and not coomer_cookies: # If cookies are enabled but none could be loaded, show help and stop. @@ -139,7 +159,7 @@ class FavoriteArtistsDialog (QDialog ): cookie_help_dialog = CookieHelpDialog(self.parent_app, self) cookie_help_dialog.exec_() self.download_button.setEnabled(False) - return # Stop further execution + return # Stop further execution kemono_fav_url ="https://kemono.su/api/v1/account/favorites?type=artist" coomer_fav_url ="https://coomer.su/api/v1/account/favorites?type=artist" @@ -149,9 +169,12 @@ class FavoriteArtistsDialog (QDialog ): errors_occurred =[] any_cookies_loaded_successfully_for_any_source =False - api_sources =[ - {"name":"Kemono.su","url":kemono_fav_url ,"domain":"kemono.su"}, - {"name":"Coomer.su","url":coomer_fav_url ,"domain":"coomer.su"} + kemono_cr_fav_url = "https://kemono.cr/api/v1/account/favorites?type=artist" + coomer_st_fav_url = "https://coomer.st/api/v1/account/favorites?type=artist" + + api_sources = [ + {"name": "Kemono.cr", "url": kemono_cr_fav_url, "domain": "kemono.cr"}, + {"name": "Coomer.st", "url": coomer_st_fav_url, "domain": "coomer.st"} ] for source in api_sources : @@ -159,20 +182,41 @@ class FavoriteArtistsDialog (QDialog ): self .status_label .setText (self ._tr ("fav_artists_loading_from_source_status","⏳ Loading favorites from {source_name}...").format (source_name =source ['name'])) QCoreApplication .processEvents () - cookies_dict_for_source =None - if self .cookies_config ['use_cookie']: - cookies_dict_for_source =prepare_cookies_for_request ( - True , - self .cookies_config ['cookie_text'], - self .cookies_config ['selected_cookie_file'], - self .cookies_config ['app_base_dir'], - self ._logger , - target_domain =source ['domain'] + cookies_dict_for_source = None + if self.cookies_config['use_cookie']: + primary_domain = source['domain'] + fallback_domain = None + if primary_domain == "kemono.cr": + fallback_domain = "kemono.su" + elif primary_domain == "coomer.st": + fallback_domain = "coomer.su" + + # First, try the primary domain + cookies_dict_for_source = prepare_cookies_for_request( + True, + self.cookies_config['cookie_text'], + self.cookies_config['selected_cookie_file'], + self.cookies_config['app_base_dir'], + self._logger, + target_domain=primary_domain ) - if cookies_dict_for_source : - any_cookies_loaded_successfully_for_any_source =True - else : - self ._logger (f"Warning ({source ['name']}): Cookies enabled but could not be loaded for this domain. Fetch might fail if cookies are required.") + + # If no cookies found, try the fallback domain + if not cookies_dict_for_source and fallback_domain: + self._logger(f"Warning ({source['name']}): No cookies found for '{primary_domain}'. Trying fallback '{fallback_domain}'...") + cookies_dict_for_source = prepare_cookies_for_request( + True, + self.cookies_config['cookie_text'], + self.cookies_config['selected_cookie_file'], + self.cookies_config['app_base_dir'], + self._logger, + target_domain=fallback_domain + ) + + if cookies_dict_for_source: + any_cookies_loaded_successfully_for_any_source = True + else: + self._logger(f"Warning ({source['name']}): Cookies enabled but could not be loaded for this source (including fallbacks). Fetch might fail.") try : headers ={'User-Agent':'Mozilla/5.0'} response =requests .get (source ['url'],headers =headers ,cookies =cookies_dict_for_source ,timeout =20 ) @@ -223,7 +267,7 @@ class FavoriteArtistsDialog (QDialog ): if self .cookies_config ['use_cookie']and not any_cookies_loaded_successfully_for_any_source : self .status_label .setText (self ._tr ("fav_artists_cookies_required_status","Error: Cookies enabled but could not be loaded for any source.")) self ._logger ("Error: Cookies enabled but no cookies loaded for any source. Showing help dialog.") - cookie_help_dialog =CookieHelpDialog (self ) + cookie_help_dialog = CookieHelpDialog(self.parent_app, self) cookie_help_dialog .exec_ () self .download_button .setEnabled (False ) if not fetched_any_successfully : diff --git a/src/ui/dialogs/FavoritePostsDialog.py b/src/ui/dialogs/FavoritePostsDialog.py index 5decc0a..38a5b69 100644 --- a/src/ui/dialogs/FavoritePostsDialog.py +++ b/src/ui/dialogs/FavoritePostsDialog.py @@ -34,28 +34,30 @@ class FavoritePostsFetcherThread (QThread ): self .target_domain_preference =target_domain_preference self .cancellation_event =threading .Event () self .error_key_map ={ - "Kemono.su":"kemono_su", - "Coomer.su":"coomer_su" + "kemono.cr":"kemono_su", + "coomer.st":"coomer_su" } def _logger (self ,message ): self .parent_logger_func (f"[FavPostsFetcherThread] {message }") - def run (self ): - kemono_fav_posts_url ="https://kemono.su/api/v1/account/favorites?type=post" - coomer_fav_posts_url ="https://coomer.su/api/v1/account/favorites?type=post" + def run(self): + kemono_su_fav_posts_url = "https://kemono.su/api/v1/account/favorites?type=post" + coomer_su_fav_posts_url = "https://coomer.su/api/v1/account/favorites?type=post" + kemono_cr_fav_posts_url = "https://kemono.cr/api/v1/account/favorites?type=post" + coomer_st_fav_posts_url = "https://coomer.st/api/v1/account/favorites?type=post" - all_fetched_posts_temp =[] - error_messages_for_summary =[] - fetched_any_successfully =False - any_cookies_loaded_successfully_for_any_source =False + all_fetched_posts_temp = [] + error_messages_for_summary = [] + fetched_any_successfully = False + any_cookies_loaded_successfully_for_any_source = False - self .status_update .emit ("key_fetching_fav_post_list_init") - self .progress_bar_update .emit (0 ,0 ) + self.status_update.emit("key_fetching_fav_post_list_init") + self.progress_bar_update.emit(0, 0) - api_sources =[ - {"name":"Kemono.su","url":kemono_fav_posts_url ,"domain":"kemono.su"}, - {"name":"Coomer.su","url":coomer_fav_posts_url ,"domain":"coomer.su"} + api_sources = [ + {"name": "Kemono.cr", "url": kemono_cr_fav_posts_url, "domain": "kemono.cr"}, + {"name": "Coomer.st", "url": coomer_st_fav_posts_url, "domain": "coomer.st"} ] api_sources_to_try =[] @@ -76,20 +78,41 @@ class FavoritePostsFetcherThread (QThread ): if self .cancellation_event .is_set (): self .finished .emit ([],"KEY_FETCH_CANCELLED_DURING") return - cookies_dict_for_source =None - if self .cookies_config ['use_cookie']: - cookies_dict_for_source =prepare_cookies_for_request ( - True , - self .cookies_config ['cookie_text'], - self .cookies_config ['selected_cookie_file'], - self .cookies_config ['app_base_dir'], - self ._logger , - target_domain =source ['domain'] + cookies_dict_for_source = None + if self.cookies_config['use_cookie']: + primary_domain = source['domain'] + fallback_domain = None + if primary_domain == "kemono.cr": + fallback_domain = "kemono.su" + elif primary_domain == "coomer.st": + fallback_domain = "coomer.su" + + # First, try the primary domain + cookies_dict_for_source = prepare_cookies_for_request( + True, + self.cookies_config['cookie_text'], + self.cookies_config['selected_cookie_file'], + self.cookies_config['app_base_dir'], + self._logger, + target_domain=primary_domain ) - if cookies_dict_for_source : - any_cookies_loaded_successfully_for_any_source =True - else : - self ._logger (f"Warning ({source ['name']}): Cookies enabled but could not be loaded for this domain. Fetch might fail if cookies are required.") + + # If no cookies found, try the fallback domain + if not cookies_dict_for_source and fallback_domain: + self._logger(f"Warning ({source['name']}): No cookies found for '{primary_domain}'. Trying fallback '{fallback_domain}'...") + cookies_dict_for_source = prepare_cookies_for_request( + True, + self.cookies_config['cookie_text'], + self.cookies_config['selected_cookie_file'], + self.cookies_config['app_base_dir'], + self._logger, + target_domain=fallback_domain + ) + + if cookies_dict_for_source: + any_cookies_loaded_successfully_for_any_source = True + else: + self._logger(f"Warning ({source['name']}): Cookies enabled but could not be loaded for this domain. Fetch might fail if cookies are required.") self ._logger (f"Attempting to fetch favorite posts from: {source ['name']} ({source ['url']})") source_key_part =self .error_key_map .get (source ['name'],source ['name'].lower ().replace ('.','_')) @@ -409,14 +432,14 @@ class FavoritePostsDialog (QDialog ): if status_key .startswith ("KEY_COOKIES_REQUIRED_BUT_NOT_FOUND_FOR_DOMAIN_")or status_key =="KEY_COOKIES_REQUIRED_BUT_NOT_FOUND_GENERIC": status_label_text_key ="fav_posts_cookies_required_error" self ._logger (f"Cookie error: {status_key }. Showing help dialog.") - cookie_help_dialog =CookieHelpDialog (self ) + cookie_help_dialog = CookieHelpDialog(self.parent_app, self) cookie_help_dialog .exec_ () elif status_key =="KEY_AUTH_FAILED": status_label_text_key ="fav_posts_auth_failed_title" self ._logger (f"Auth error: {status_key }. Showing help dialog.") QMessageBox .warning (self ,self ._tr ("fav_posts_auth_failed_title","Authorization Failed (Posts)"), self ._tr ("fav_posts_auth_failed_message_generic","...").format (domain_specific_part =specific_domain_msg_part )) - cookie_help_dialog =CookieHelpDialog (self ) + cookie_help_dialog = CookieHelpDialog(self.parent_app, self) cookie_help_dialog .exec_ () elif status_key =="KEY_NO_FAVORITES_FOUND_ALL_PLATFORMS": status_label_text_key ="fav_posts_no_posts_found_status" diff --git a/src/ui/dialogs/multipart_dialog.py b/src/ui/dialogs/multipart_dialog.py new file mode 100644 index 0000000..a05ac3c --- /dev/null +++ b/src/ui/dialogs/multipart_dialog.py @@ -0,0 +1,81 @@ +from PyQt5.QtWidgets import ( + QDialog, QVBoxLayout, QGroupBox, QCheckBox, QHBoxLayout, QLabel, + QLineEdit, QDialogButtonBox, QIntValidator +) +from PyQt5.QtCore import Qt + +class MultipartDialog(QDialog): + """ + A dialog for configuring multipart download settings. + """ + def __init__(self, current_settings, parent=None): + super().__init__(parent) + self.setWindowTitle("Multipart Download Options") + self.setMinimumWidth(350) + + self.settings = current_settings + + # Main layout + layout = QVBoxLayout(self) + + # File types group + types_group = QGroupBox("Apply to File Types") + types_layout = QVBoxLayout() + self.videos_checkbox = QCheckBox("Videos") + self.archives_checkbox = QCheckBox("Archives") + types_layout.addWidget(self.videos_checkbox) + types_layout.addWidget(self.archives_checkbox) + types_group.setLayout(types_layout) + layout.addWidget(types_group) + + # File size group + size_group = QGroupBox("Minimum File Size") + size_layout = QHBoxLayout() + size_layout.addWidget(QLabel("Apply only if file size is over:")) + self.min_size_input = QLineEdit() + self.min_size_input.setValidator(QIntValidator(0, 99999)) + self.min_size_input.setFixedWidth(50) + size_layout.addWidget(self.min_size_input) + size_layout.addWidget(QLabel("MB")) + size_layout.addStretch() + size_group.setLayout(size_layout) + layout.addWidget(size_group) + + # Custom extensions group + extensions_group = QGroupBox("Custom File Extensions") + extensions_layout = QVBoxLayout() + extensions_layout.addWidget(QLabel("Apply to these additional extensions (comma-separated):")) + self.extensions_input = QLineEdit() + self.extensions_input.setPlaceholderText("e.g., .psd, .blend, .mkv") + extensions_layout.addWidget(self.extensions_input) + extensions_group.setLayout(extensions_layout) + layout.addWidget(extensions_group) + + # Dialog buttons + button_box = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel) + button_box.accepted.connect(self.accept) + button_box.rejected.connect(self.reject) + layout.addWidget(button_box) + + self.setLayout(layout) + self._load_initial_settings() + + def _load_initial_settings(self): + """Populates the dialog with the current settings.""" + self.videos_checkbox.setChecked(self.settings.get('apply_on_videos', False)) + self.archives_checkbox.setChecked(self.settings.get('apply_on_archives', False)) + self.min_size_input.setText(str(self.settings.get('min_size_mb', 200))) + self.extensions_input.setText(", ".join(self.settings.get('custom_extensions', []))) + + def get_selected_options(self): + """Returns the configured settings from the dialog.""" + custom_extensions_raw = self.extensions_input.text().strip().lower() + custom_extensions = {ext.strip() for ext in custom_extensions_raw.split(',') if ext.strip().startswith('.')} + + return { + "enabled": True, # Implied if dialog is saved + "apply_on_videos": self.videos_checkbox.isChecked(), + "apply_on_archives": self.archives_checkbox.isChecked(), + "min_size_mb": int(self.min_size_input.text()) if self.min_size_input.text().isdigit() else 200, + "custom_extensions": sorted(list(custom_extensions)) + } diff --git a/src/ui/main_window.py b/src/ui/main_window.py index 9ab046e..4f30ad2 100644 --- a/src/ui/main_window.py +++ b/src/ui/main_window.py @@ -4288,13 +4288,13 @@ class DownloaderApp (QWidget ): self.log_signal.emit(" Cancelling active External Link download thread...") self.external_link_download_thread.cancel() - def _get_domain_for_service (self ,service_name :str )->str : + def _get_domain_for_service(self, service_name: str) -> str: """Determines the base domain for a given service.""" - if not isinstance (service_name ,str ): - return "kemono.cr" - service_lower =service_name .lower () - coomer_primary_services ={'onlyfans','fansly','manyvids','candfans','gumroad','patreon','subscribestar','dlsite','discord','fantia','boosty','pixiv','fanbox'} - if service_lower in coomer_primary_services and service_lower not in ['patreon','discord','fantia','boosty','pixiv','fanbox']: + if not isinstance(service_name, str): + return "kemono.cr" # Default fallback + service_lower = service_name.lower() + coomer_primary_services = {'onlyfans', 'fansly', 'manyvids', 'candfans', 'gumroad', 'subscribestar', 'dlsite'} + if service_lower in coomer_primary_services: return "coomer.st" return "kemono.cr" @@ -5343,42 +5343,54 @@ class DownloaderApp (QWidget ): target_domain_preference_for_fetch =None - if cookies_config ['use_cookie']: - self .log_signal .emit ("Favorite Posts: 'Use Cookie' is checked. Determining target domain...") - kemono_cookies =prepare_cookies_for_request ( - cookies_config ['use_cookie'], - cookies_config ['cookie_text'], - cookies_config ['selected_cookie_file'], - cookies_config ['app_base_dir'], - lambda msg :self .log_signal .emit (f"[FavPosts Cookie Check - Kemono] {msg }"), - target_domain ="kemono.su" + if cookies_config['use_cookie']: + self.log_signal.emit("Favorite Posts: 'Use Cookie' is checked. Determining target domain...") + + # --- Kemono Check with Fallback --- + kemono_cookies = prepare_cookies_for_request( + cookies_config['use_cookie'], cookies_config['cookie_text'], cookies_config['selected_cookie_file'], + cookies_config['app_base_dir'], lambda msg: self.log_signal.emit(f"[FavPosts Cookie Check] {msg}"), + target_domain="kemono.cr" ) - coomer_cookies =prepare_cookies_for_request ( - cookies_config ['use_cookie'], - cookies_config ['cookie_text'], - cookies_config ['selected_cookie_file'], - cookies_config ['app_base_dir'], - lambda msg :self .log_signal .emit (f"[FavPosts Cookie Check - Coomer] {msg }"), - target_domain ="coomer.su" + if not kemono_cookies: + self.log_signal.emit(" ↳ No cookies for kemono.cr, trying fallback kemono.su...") + kemono_cookies = prepare_cookies_for_request( + cookies_config['use_cookie'], cookies_config['cookie_text'], cookies_config['selected_cookie_file'], + cookies_config['app_base_dir'], lambda msg: self.log_signal.emit(f"[FavPosts Cookie Check] {msg}"), + target_domain="kemono.su" + ) + + # --- Coomer Check with Fallback --- + coomer_cookies = prepare_cookies_for_request( + cookies_config['use_cookie'], cookies_config['cookie_text'], cookies_config['selected_cookie_file'], + cookies_config['app_base_dir'], lambda msg: self.log_signal.emit(f"[FavPosts Cookie Check] {msg}"), + target_domain="coomer.st" ) + if not coomer_cookies: + self.log_signal.emit(" ↳ No cookies for coomer.st, trying fallback coomer.su...") + coomer_cookies = prepare_cookies_for_request( + cookies_config['use_cookie'], cookies_config['cookie_text'], cookies_config['selected_cookie_file'], + cookies_config['app_base_dir'], lambda msg: self.log_signal.emit(f"[FavPosts Cookie Check] {msg}"), + target_domain="coomer.su" + ) - kemono_ok =bool (kemono_cookies ) - coomer_ok =bool (coomer_cookies ) + kemono_ok = bool(kemono_cookies) + coomer_ok = bool(coomer_cookies) - if kemono_ok and not coomer_ok : - target_domain_preference_for_fetch ="kemono.su" - self .log_signal .emit (" ↳ Only Kemono.su cookies loaded. Will fetch favorites from Kemono.su only.") - elif coomer_ok and not kemono_ok : - target_domain_preference_for_fetch ="coomer.su" - self .log_signal .emit (" ↳ Only Coomer.su cookies loaded. Will fetch favorites from Coomer.su only.") - elif kemono_ok and coomer_ok : - target_domain_preference_for_fetch =None - self .log_signal .emit (" ↳ Cookies for both Kemono.su and Coomer.su loaded. Will attempt to fetch from both.") - else : - self .log_signal .emit (" ↳ No valid cookies loaded for Kemono.su or Coomer.su.") - cookie_help_dialog =CookieHelpDialog (self ,self ) - cookie_help_dialog .exec_ () - return + if kemono_ok and not coomer_ok: + target_domain_preference_for_fetch = "kemono.cr" + self.log_signal.emit(" ↳ Only Kemono cookies loaded. Will fetch favorites from Kemono.cr only.") + elif coomer_ok and not kemono_ok: + target_domain_preference_for_fetch = "coomer.st" + self.log_signal.emit(" ↳ Only Coomer cookies loaded. Will fetch favorites from Coomer.st only.") + elif kemono_ok and coomer_ok: + target_domain_preference_for_fetch = None + self.log_signal.emit(" ↳ Cookies for both Kemono and Coomer loaded. Will attempt to fetch from both.") + else: + self.log_signal.emit(" ↳ No valid cookies loaded for Kemono.cr or Coomer.st.") + cookie_help_dialog = CookieHelpDialog(self, self) + cookie_help_dialog.exec_() + return else : self .log_signal .emit ("Favorite Posts: 'Use Cookie' is NOT checked. Cookies are required.") cookie_help_dialog =CookieHelpDialog (self ,self )