mirror of
https://github.com/Yuvi9587/Kemono-Downloader.git
synced 2025-12-29 16:14:44 +00:00
commit
This commit is contained in:
@@ -410,6 +410,39 @@ class PostProcessorWorker:
|
|||||||
unique_id_for_part_file = uuid.uuid4().hex[:8]
|
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}"
|
unique_part_file_stem_on_disk = f"{temp_file_base_for_unique_part}_{unique_id_for_part_file}"
|
||||||
max_retries = 3
|
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
|
retry_delay = 5
|
||||||
downloaded_size_bytes = 0
|
downloaded_size_bytes = 0
|
||||||
calculated_file_hash = None
|
calculated_file_hash = None
|
||||||
@@ -741,8 +774,11 @@ class PostProcessorWorker:
|
|||||||
history_data_for_this_post = None
|
history_data_for_this_post = None
|
||||||
|
|
||||||
parsed_api_url = urlparse(self.api_url_input)
|
parsed_api_url = urlparse(self.api_url_input)
|
||||||
referer_url = f"https://{parsed_api_url.netloc}/"
|
post_data = self.post
|
||||||
headers = {'User-Agent': 'Mozilla/5.0', 'Referer': referer_url, 'Accept': '*/*'}
|
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"""<a\s+.*?href=["'](https?://[^"']+)["'][^>]*>(.*?)</a>""", re.IGNORECASE | re.DOTALL)
|
link_pattern = re.compile(r"""<a\s+.*?href=["'](https?://[^"']+)["'][^>]*>(.*?)</a>""", re.IGNORECASE | re.DOTALL)
|
||||||
post_data = self.post
|
post_data = self.post
|
||||||
post_title = post_data.get('title', '') or 'untitled_post'
|
post_title = post_data.get('title', '') or 'untitled_post'
|
||||||
@@ -802,7 +838,7 @@ class PostProcessorWorker:
|
|||||||
all_files_from_post_api_for_char_check = []
|
all_files_from_post_api_for_char_check = []
|
||||||
api_file_domain_for_char_check = urlparse(self.api_url_input).netloc
|
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']):
|
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'):
|
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('/'))
|
original_api_name = post_main_file_info.get('name') or os.path.basename(post_main_file_info['path'].lstrip('/'))
|
||||||
if original_api_name:
|
if original_api_name:
|
||||||
@@ -845,9 +881,9 @@ class PostProcessorWorker:
|
|||||||
try:
|
try:
|
||||||
parsed_input_url_for_comments = urlparse(self.api_url_input)
|
parsed_input_url_for_comments = urlparse(self.api_url_input)
|
||||||
api_domain_for_comments = parsed_input_url_for_comments.netloc
|
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.")
|
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(
|
comments_data = fetch_post_comments(
|
||||||
api_domain_for_comments, self.service, self.user_id, post_id,
|
api_domain_for_comments, self.service, self.user_id, post_id,
|
||||||
headers, self.logger, self.cancellation_event, self.pause_event,
|
headers, self.logger, self.cancellation_event, self.pause_event,
|
||||||
@@ -1332,7 +1368,7 @@ class PostProcessorWorker:
|
|||||||
all_files_from_post_api = []
|
all_files_from_post_api = []
|
||||||
api_file_domain = urlparse(self.api_url_input).netloc
|
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']):
|
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'):
|
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('/')
|
file_path = post_main_file_info['path'].lstrip('/')
|
||||||
original_api_name = post_main_file_info.get('name') or os.path.basename(file_path)
|
original_api_name = post_main_file_info.get('name') or os.path.basename(file_path)
|
||||||
|
|||||||
@@ -37,13 +37,13 @@ class FavoriteArtistsDialog (QDialog ):
|
|||||||
self ._init_ui ()
|
self ._init_ui ()
|
||||||
self ._fetch_favorite_artists ()
|
self ._fetch_favorite_artists ()
|
||||||
|
|
||||||
def _get_domain_for_service (self ,service_name ):
|
def _get_domain_for_service(self, service_name):
|
||||||
service_lower =service_name .lower ()
|
service_lower = service_name.lower()
|
||||||
coomer_primary_services ={'onlyfans','fansly','manyvids','candfans'}
|
coomer_primary_services = {'onlyfans', 'fansly', 'manyvids', 'candfans'}
|
||||||
if service_lower in coomer_primary_services :
|
if service_lower in coomer_primary_services:
|
||||||
return "coomer.su"
|
return "coomer.st" # Use the new domain
|
||||||
else :
|
else:
|
||||||
return "kemono.su"
|
return "kemono.cr" # Use the new domain
|
||||||
|
|
||||||
def _tr (self ,key ,default_text =""):
|
def _tr (self ,key ,default_text =""):
|
||||||
"""Helper to get translation based on current app language."""
|
"""Helper to get translation based on current app language."""
|
||||||
@@ -128,9 +128,29 @@ class FavoriteArtistsDialog (QDialog ):
|
|||||||
def _fetch_favorite_artists (self ):
|
def _fetch_favorite_artists (self ):
|
||||||
|
|
||||||
if self.cookies_config['use_cookie']:
|
if self.cookies_config['use_cookie']:
|
||||||
# Check if we can load cookies for at least one of the services.
|
# --- 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.su")
|
kemono_cookies = prepare_cookies_for_request(
|
||||||
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")
|
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 not kemono_cookies and not coomer_cookies:
|
||||||
# If cookies are enabled but none could be loaded, show help and stop.
|
# 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 = CookieHelpDialog(self.parent_app, self)
|
||||||
cookie_help_dialog.exec_()
|
cookie_help_dialog.exec_()
|
||||||
self.download_button.setEnabled(False)
|
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"
|
kemono_fav_url ="https://kemono.su/api/v1/account/favorites?type=artist"
|
||||||
coomer_fav_url ="https://coomer.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 =[]
|
errors_occurred =[]
|
||||||
any_cookies_loaded_successfully_for_any_source =False
|
any_cookies_loaded_successfully_for_any_source =False
|
||||||
|
|
||||||
api_sources =[
|
kemono_cr_fav_url = "https://kemono.cr/api/v1/account/favorites?type=artist"
|
||||||
{"name":"Kemono.su","url":kemono_fav_url ,"domain":"kemono.su"},
|
coomer_st_fav_url = "https://coomer.st/api/v1/account/favorites?type=artist"
|
||||||
{"name":"Coomer.su","url":coomer_fav_url ,"domain":"coomer.su"}
|
|
||||||
|
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 :
|
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']))
|
self .status_label .setText (self ._tr ("fav_artists_loading_from_source_status","⏳ Loading favorites from {source_name}...").format (source_name =source ['name']))
|
||||||
QCoreApplication .processEvents ()
|
QCoreApplication .processEvents ()
|
||||||
|
|
||||||
cookies_dict_for_source =None
|
cookies_dict_for_source = None
|
||||||
if self .cookies_config ['use_cookie']:
|
if self.cookies_config['use_cookie']:
|
||||||
cookies_dict_for_source =prepare_cookies_for_request (
|
primary_domain = source['domain']
|
||||||
True ,
|
fallback_domain = None
|
||||||
self .cookies_config ['cookie_text'],
|
if primary_domain == "kemono.cr":
|
||||||
self .cookies_config ['selected_cookie_file'],
|
fallback_domain = "kemono.su"
|
||||||
self .cookies_config ['app_base_dir'],
|
elif primary_domain == "coomer.st":
|
||||||
self ._logger ,
|
fallback_domain = "coomer.su"
|
||||||
target_domain =source ['domain']
|
|
||||||
|
# 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
|
# If no cookies found, try the fallback domain
|
||||||
else :
|
if not cookies_dict_for_source and fallback_domain:
|
||||||
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"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 :
|
try :
|
||||||
headers ={'User-Agent':'Mozilla/5.0'}
|
headers ={'User-Agent':'Mozilla/5.0'}
|
||||||
response =requests .get (source ['url'],headers =headers ,cookies =cookies_dict_for_source ,timeout =20 )
|
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 :
|
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 .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.")
|
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_ ()
|
cookie_help_dialog .exec_ ()
|
||||||
self .download_button .setEnabled (False )
|
self .download_button .setEnabled (False )
|
||||||
if not fetched_any_successfully :
|
if not fetched_any_successfully :
|
||||||
|
|||||||
@@ -34,28 +34,30 @@ class FavoritePostsFetcherThread (QThread ):
|
|||||||
self .target_domain_preference =target_domain_preference
|
self .target_domain_preference =target_domain_preference
|
||||||
self .cancellation_event =threading .Event ()
|
self .cancellation_event =threading .Event ()
|
||||||
self .error_key_map ={
|
self .error_key_map ={
|
||||||
"Kemono.su":"kemono_su",
|
"kemono.cr":"kemono_su",
|
||||||
"Coomer.su":"coomer_su"
|
"coomer.st":"coomer_su"
|
||||||
}
|
}
|
||||||
|
|
||||||
def _logger (self ,message ):
|
def _logger (self ,message ):
|
||||||
self .parent_logger_func (f"[FavPostsFetcherThread] {message }")
|
self .parent_logger_func (f"[FavPostsFetcherThread] {message }")
|
||||||
|
|
||||||
def run (self ):
|
def run(self):
|
||||||
kemono_fav_posts_url ="https://kemono.su/api/v1/account/favorites?type=post"
|
kemono_su_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"
|
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 =[]
|
all_fetched_posts_temp = []
|
||||||
error_messages_for_summary =[]
|
error_messages_for_summary = []
|
||||||
fetched_any_successfully =False
|
fetched_any_successfully = False
|
||||||
any_cookies_loaded_successfully_for_any_source =False
|
any_cookies_loaded_successfully_for_any_source = False
|
||||||
|
|
||||||
self .status_update .emit ("key_fetching_fav_post_list_init")
|
self.status_update.emit("key_fetching_fav_post_list_init")
|
||||||
self .progress_bar_update .emit (0 ,0 )
|
self.progress_bar_update.emit(0, 0)
|
||||||
|
|
||||||
api_sources =[
|
api_sources = [
|
||||||
{"name":"Kemono.su","url":kemono_fav_posts_url ,"domain":"kemono.su"},
|
{"name": "Kemono.cr", "url": kemono_cr_fav_posts_url, "domain": "kemono.cr"},
|
||||||
{"name":"Coomer.su","url":coomer_fav_posts_url ,"domain":"coomer.su"}
|
{"name": "Coomer.st", "url": coomer_st_fav_posts_url, "domain": "coomer.st"}
|
||||||
]
|
]
|
||||||
|
|
||||||
api_sources_to_try =[]
|
api_sources_to_try =[]
|
||||||
@@ -76,20 +78,41 @@ class FavoritePostsFetcherThread (QThread ):
|
|||||||
if self .cancellation_event .is_set ():
|
if self .cancellation_event .is_set ():
|
||||||
self .finished .emit ([],"KEY_FETCH_CANCELLED_DURING")
|
self .finished .emit ([],"KEY_FETCH_CANCELLED_DURING")
|
||||||
return
|
return
|
||||||
cookies_dict_for_source =None
|
cookies_dict_for_source = None
|
||||||
if self .cookies_config ['use_cookie']:
|
if self.cookies_config['use_cookie']:
|
||||||
cookies_dict_for_source =prepare_cookies_for_request (
|
primary_domain = source['domain']
|
||||||
True ,
|
fallback_domain = None
|
||||||
self .cookies_config ['cookie_text'],
|
if primary_domain == "kemono.cr":
|
||||||
self .cookies_config ['selected_cookie_file'],
|
fallback_domain = "kemono.su"
|
||||||
self .cookies_config ['app_base_dir'],
|
elif primary_domain == "coomer.st":
|
||||||
self ._logger ,
|
fallback_domain = "coomer.su"
|
||||||
target_domain =source ['domain']
|
|
||||||
|
# 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
|
# If no cookies found, try the fallback domain
|
||||||
else :
|
if not cookies_dict_for_source and fallback_domain:
|
||||||
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"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']})")
|
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 ('.','_'))
|
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":
|
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"
|
status_label_text_key ="fav_posts_cookies_required_error"
|
||||||
self ._logger (f"Cookie error: {status_key }. Showing help dialog.")
|
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_ ()
|
cookie_help_dialog .exec_ ()
|
||||||
elif status_key =="KEY_AUTH_FAILED":
|
elif status_key =="KEY_AUTH_FAILED":
|
||||||
status_label_text_key ="fav_posts_auth_failed_title"
|
status_label_text_key ="fav_posts_auth_failed_title"
|
||||||
self ._logger (f"Auth error: {status_key }. Showing help dialog.")
|
self ._logger (f"Auth error: {status_key }. Showing help dialog.")
|
||||||
QMessageBox .warning (self ,self ._tr ("fav_posts_auth_failed_title","Authorization Failed (Posts)"),
|
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 ))
|
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_ ()
|
cookie_help_dialog .exec_ ()
|
||||||
elif status_key =="KEY_NO_FAVORITES_FOUND_ALL_PLATFORMS":
|
elif status_key =="KEY_NO_FAVORITES_FOUND_ALL_PLATFORMS":
|
||||||
status_label_text_key ="fav_posts_no_posts_found_status"
|
status_label_text_key ="fav_posts_no_posts_found_status"
|
||||||
|
|||||||
81
src/ui/dialogs/multipart_dialog.py
Normal file
81
src/ui/dialogs/multipart_dialog.py
Normal file
@@ -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))
|
||||||
|
}
|
||||||
@@ -4288,13 +4288,13 @@ class DownloaderApp (QWidget ):
|
|||||||
self.log_signal.emit(" Cancelling active External Link download thread...")
|
self.log_signal.emit(" Cancelling active External Link download thread...")
|
||||||
self.external_link_download_thread.cancel()
|
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."""
|
"""Determines the base domain for a given service."""
|
||||||
if not isinstance (service_name ,str ):
|
if not isinstance(service_name, str):
|
||||||
return "kemono.cr"
|
return "kemono.cr" # Default fallback
|
||||||
service_lower =service_name .lower ()
|
service_lower = service_name.lower()
|
||||||
coomer_primary_services ={'onlyfans','fansly','manyvids','candfans','gumroad','patreon','subscribestar','dlsite','discord','fantia','boosty','pixiv','fanbox'}
|
coomer_primary_services = {'onlyfans', 'fansly', 'manyvids', 'candfans', 'gumroad', 'subscribestar', 'dlsite'}
|
||||||
if service_lower in coomer_primary_services and service_lower not in ['patreon','discord','fantia','boosty','pixiv','fanbox']:
|
if service_lower in coomer_primary_services:
|
||||||
return "coomer.st"
|
return "coomer.st"
|
||||||
return "kemono.cr"
|
return "kemono.cr"
|
||||||
|
|
||||||
@@ -5343,42 +5343,54 @@ class DownloaderApp (QWidget ):
|
|||||||
|
|
||||||
target_domain_preference_for_fetch =None
|
target_domain_preference_for_fetch =None
|
||||||
|
|
||||||
if cookies_config ['use_cookie']:
|
if cookies_config['use_cookie']:
|
||||||
self .log_signal .emit ("Favorite Posts: 'Use Cookie' is checked. Determining target domain...")
|
self.log_signal.emit("Favorite Posts: 'Use Cookie' is checked. Determining target domain...")
|
||||||
kemono_cookies =prepare_cookies_for_request (
|
|
||||||
cookies_config ['use_cookie'],
|
# --- Kemono Check with Fallback ---
|
||||||
cookies_config ['cookie_text'],
|
kemono_cookies = prepare_cookies_for_request(
|
||||||
cookies_config ['selected_cookie_file'],
|
cookies_config['use_cookie'], cookies_config['cookie_text'], cookies_config['selected_cookie_file'],
|
||||||
cookies_config ['app_base_dir'],
|
cookies_config['app_base_dir'], lambda msg: self.log_signal.emit(f"[FavPosts Cookie Check] {msg}"),
|
||||||
lambda msg :self .log_signal .emit (f"[FavPosts Cookie Check - Kemono] {msg }"),
|
target_domain="kemono.cr"
|
||||||
target_domain ="kemono.su"
|
|
||||||
)
|
)
|
||||||
coomer_cookies =prepare_cookies_for_request (
|
if not kemono_cookies:
|
||||||
cookies_config ['use_cookie'],
|
self.log_signal.emit(" ↳ No cookies for kemono.cr, trying fallback kemono.su...")
|
||||||
cookies_config ['cookie_text'],
|
kemono_cookies = prepare_cookies_for_request(
|
||||||
cookies_config ['selected_cookie_file'],
|
cookies_config['use_cookie'], cookies_config['cookie_text'], cookies_config['selected_cookie_file'],
|
||||||
cookies_config ['app_base_dir'],
|
cookies_config['app_base_dir'], lambda msg: self.log_signal.emit(f"[FavPosts Cookie Check] {msg}"),
|
||||||
lambda msg :self .log_signal .emit (f"[FavPosts Cookie Check - Coomer] {msg }"),
|
target_domain="kemono.su"
|
||||||
target_domain ="coomer.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 )
|
kemono_ok = bool(kemono_cookies)
|
||||||
coomer_ok =bool (coomer_cookies )
|
coomer_ok = bool(coomer_cookies)
|
||||||
|
|
||||||
if kemono_ok and not coomer_ok :
|
if kemono_ok and not coomer_ok:
|
||||||
target_domain_preference_for_fetch ="kemono.su"
|
target_domain_preference_for_fetch = "kemono.cr"
|
||||||
self .log_signal .emit (" ↳ Only Kemono.su cookies loaded. Will fetch favorites from Kemono.su only.")
|
self.log_signal.emit(" ↳ Only Kemono cookies loaded. Will fetch favorites from Kemono.cr only.")
|
||||||
elif coomer_ok and not kemono_ok :
|
elif coomer_ok and not kemono_ok:
|
||||||
target_domain_preference_for_fetch ="coomer.su"
|
target_domain_preference_for_fetch = "coomer.st"
|
||||||
self .log_signal .emit (" ↳ Only Coomer.su cookies loaded. Will fetch favorites from Coomer.su only.")
|
self.log_signal.emit(" ↳ Only Coomer cookies loaded. Will fetch favorites from Coomer.st only.")
|
||||||
elif kemono_ok and coomer_ok :
|
elif kemono_ok and coomer_ok:
|
||||||
target_domain_preference_for_fetch =None
|
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.")
|
self.log_signal.emit(" ↳ Cookies for both Kemono and Coomer loaded. Will attempt to fetch from both.")
|
||||||
else :
|
else:
|
||||||
self .log_signal .emit (" ↳ No valid cookies loaded for Kemono.su or Coomer.su.")
|
self.log_signal.emit(" ↳ No valid cookies loaded for Kemono.cr or Coomer.st.")
|
||||||
cookie_help_dialog =CookieHelpDialog (self ,self )
|
cookie_help_dialog = CookieHelpDialog(self, self)
|
||||||
cookie_help_dialog .exec_ ()
|
cookie_help_dialog.exec_()
|
||||||
return
|
return
|
||||||
else :
|
else :
|
||||||
self .log_signal .emit ("Favorite Posts: 'Use Cookie' is NOT checked. Cookies are required.")
|
self .log_signal .emit ("Favorite Posts: 'Use Cookie' is NOT checked. Cookies are required.")
|
||||||
cookie_help_dialog =CookieHelpDialog (self ,self )
|
cookie_help_dialog =CookieHelpDialog (self ,self )
|
||||||
|
|||||||
Reference in New Issue
Block a user