mirror of
https://github.com/Yuvi9587/Kemono-Downloader.git
synced 2025-12-29 16:14:44 +00:00
Commit
This commit is contained in:
288
src/ui/dialogs/FavoriteArtistsDialog.py
Normal file
288
src/ui/dialogs/FavoriteArtistsDialog.py
Normal file
@@ -0,0 +1,288 @@
|
||||
# --- Standard Library Imports ---
|
||||
import html
|
||||
import re
|
||||
|
||||
# --- Third-Party Library Imports ---
|
||||
import requests
|
||||
from PyQt5.QtCore import QCoreApplication, Qt
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication, QDialog, QHBoxLayout, QLabel, QLineEdit, QListWidget,
|
||||
QListWidgetItem, QMessageBox, QPushButton, QVBoxLayout
|
||||
)
|
||||
|
||||
# --- Local Application Imports ---
|
||||
from ...i18n.translator import get_translation
|
||||
# Corrected Import: Get the icon from the new assets utility module
|
||||
from ..assets import get_app_icon_object
|
||||
from ...utils.network_utils import prepare_cookies_for_request
|
||||
from .CookieHelpDialog import CookieHelpDialog
|
||||
|
||||
|
||||
class FavoriteArtistsDialog (QDialog ):
|
||||
"""Dialog to display and select favorite artists."""
|
||||
def __init__ (self ,parent_app ,cookies_config ):
|
||||
super ().__init__ (parent_app )
|
||||
self .parent_app =parent_app
|
||||
self .cookies_config =cookies_config
|
||||
self .all_fetched_artists =[]
|
||||
|
||||
app_icon =get_app_icon_object ()
|
||||
if not app_icon .isNull ():
|
||||
self .setWindowIcon (app_icon )
|
||||
self .selected_artist_urls =[]
|
||||
|
||||
self .setModal (True )
|
||||
self .setMinimumSize (500 ,500 )
|
||||
|
||||
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 _tr (self ,key ,default_text =""):
|
||||
"""Helper to get translation based on current app language."""
|
||||
if callable (get_translation )and self .parent_app :
|
||||
return get_translation (self .parent_app .current_selected_language ,key ,default_text )
|
||||
return default_text
|
||||
|
||||
def _retranslate_ui (self ):
|
||||
self .setWindowTitle (self ._tr ("fav_artists_dialog_title","Favorite Artists"))
|
||||
self .status_label .setText (self ._tr ("fav_artists_loading_status","Loading favorite artists..."))
|
||||
self .search_input .setPlaceholderText (self ._tr ("fav_artists_search_placeholder","Search artists..."))
|
||||
self .select_all_button .setText (self ._tr ("fav_artists_select_all_button","Select All"))
|
||||
self .deselect_all_button .setText (self ._tr ("fav_artists_deselect_all_button","Deselect All"))
|
||||
self .download_button .setText (self ._tr ("fav_artists_download_selected_button","Download Selected"))
|
||||
self .cancel_button .setText (self ._tr ("fav_artists_cancel_button","Cancel"))
|
||||
|
||||
def _init_ui (self ):
|
||||
main_layout =QVBoxLayout (self )
|
||||
|
||||
self .status_label =QLabel ()
|
||||
self .status_label .setAlignment (Qt .AlignCenter )
|
||||
main_layout .addWidget (self .status_label )
|
||||
|
||||
self .search_input =QLineEdit ()
|
||||
self .search_input .textChanged .connect (self ._filter_artist_list_display )
|
||||
main_layout .addWidget (self .search_input )
|
||||
|
||||
|
||||
self .artist_list_widget =QListWidget ()
|
||||
self .artist_list_widget .setStyleSheet ("""
|
||||
QListWidget::item {
|
||||
border-bottom: 1px solid #4A4A4A; /* Slightly softer line */
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}""")
|
||||
main_layout .addWidget (self .artist_list_widget )
|
||||
self .artist_list_widget .setAlternatingRowColors (True )
|
||||
self .search_input .setVisible (False )
|
||||
self .artist_list_widget .setVisible (False )
|
||||
|
||||
combined_buttons_layout =QHBoxLayout ()
|
||||
|
||||
self .select_all_button =QPushButton ()
|
||||
self .select_all_button .clicked .connect (self ._select_all_items )
|
||||
combined_buttons_layout .addWidget (self .select_all_button )
|
||||
|
||||
self .deselect_all_button =QPushButton ()
|
||||
self .deselect_all_button .clicked .connect (self ._deselect_all_items )
|
||||
combined_buttons_layout .addWidget (self .deselect_all_button )
|
||||
|
||||
|
||||
self .download_button =QPushButton ()
|
||||
self .download_button .clicked .connect (self ._accept_selection_action )
|
||||
self .download_button .setEnabled (False )
|
||||
self .download_button .setDefault (True )
|
||||
combined_buttons_layout .addWidget (self .download_button )
|
||||
|
||||
self .cancel_button =QPushButton ()
|
||||
self .cancel_button .clicked .connect (self .reject )
|
||||
combined_buttons_layout .addWidget (self .cancel_button )
|
||||
|
||||
combined_buttons_layout .addStretch (1 )
|
||||
main_layout .addLayout (combined_buttons_layout )
|
||||
|
||||
self ._retranslate_ui ()
|
||||
if hasattr (self .parent_app ,'get_dark_theme')and self .parent_app .current_theme =="dark":
|
||||
self .setStyleSheet (self .parent_app .get_dark_theme ())
|
||||
|
||||
|
||||
def _logger (self ,message ):
|
||||
"""Helper to log messages, either to parent app or console."""
|
||||
if hasattr (self .parent_app ,'log_signal')and self .parent_app .log_signal :
|
||||
self .parent_app .log_signal .emit (f"[FavArtistsDialog] {message }")
|
||||
else :
|
||||
print (f"[FavArtistsDialog] {message }")
|
||||
|
||||
def _show_content_elements (self ,show ):
|
||||
"""Helper to show/hide content-related widgets."""
|
||||
self .search_input .setVisible (show )
|
||||
self .artist_list_widget .setVisible (show )
|
||||
|
||||
def _fetch_favorite_artists (self ):
|
||||
kemono_fav_url ="https://kemono.su/api/v1/account/favorites?type=artist"
|
||||
coomer_fav_url ="https://coomer.su/api/v1/account/favorites?type=artist"
|
||||
|
||||
self .all_fetched_artists =[]
|
||||
fetched_any_successfully =False
|
||||
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"}
|
||||
]
|
||||
|
||||
for source in api_sources :
|
||||
self ._logger (f"Attempting to fetch favorite artists from: {source ['name']} ({source ['url']})")
|
||||
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']
|
||||
)
|
||||
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.")
|
||||
try :
|
||||
headers ={'User-Agent':'Mozilla/5.0'}
|
||||
response =requests .get (source ['url'],headers =headers ,cookies =cookies_dict_for_source ,timeout =20 )
|
||||
response .raise_for_status ()
|
||||
artists_data_from_api =response .json ()
|
||||
|
||||
if not isinstance (artists_data_from_api ,list ):
|
||||
error_msg =f"Error ({source ['name']}): API did not return a list of artists (got {type (artists_data_from_api )})."
|
||||
self ._logger (error_msg )
|
||||
errors_occurred .append (error_msg )
|
||||
continue
|
||||
|
||||
processed_artists_from_source =0
|
||||
for artist_entry in artists_data_from_api :
|
||||
artist_id =artist_entry .get ("id")
|
||||
artist_name =html .unescape (artist_entry .get ("name","Unknown Artist").strip ())
|
||||
artist_service_platform =artist_entry .get ("service")
|
||||
|
||||
if artist_id and artist_name and artist_service_platform :
|
||||
artist_page_domain =self ._get_domain_for_service (artist_service_platform )
|
||||
full_url =f"https://{artist_page_domain }/{artist_service_platform }/user/{artist_id }"
|
||||
|
||||
self .all_fetched_artists .append ({
|
||||
'name':artist_name ,
|
||||
'url':full_url ,
|
||||
'service':artist_service_platform ,
|
||||
'id':artist_id ,
|
||||
'_source_api':source ['name']
|
||||
})
|
||||
processed_artists_from_source +=1
|
||||
else :
|
||||
self ._logger (f"Warning ({source ['name']}): Skipping favorite artist entry due to missing data: {artist_entry }")
|
||||
|
||||
if processed_artists_from_source >0 :
|
||||
fetched_any_successfully =True
|
||||
self ._logger (f"Fetched {processed_artists_from_source } artists from {source ['name']}.")
|
||||
|
||||
except requests .exceptions .RequestException as e :
|
||||
error_msg =f"Error fetching favorites from {source ['name']}: {e }"
|
||||
self ._logger (error_msg )
|
||||
errors_occurred .append (error_msg )
|
||||
except Exception as e :
|
||||
error_msg =f"An unexpected error occurred with {source ['name']}: {e }"
|
||||
self ._logger (error_msg )
|
||||
errors_occurred .append (error_msg )
|
||||
|
||||
|
||||
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 .exec_ ()
|
||||
self .download_button .setEnabled (False )
|
||||
if not fetched_any_successfully :
|
||||
errors_occurred .append ("Cookies enabled but could not be loaded for any API source.")
|
||||
|
||||
unique_artists_map ={}
|
||||
for artist in self .all_fetched_artists :
|
||||
key =(artist ['service'].lower (),str (artist ['id']).lower ())
|
||||
if key not in unique_artists_map :
|
||||
unique_artists_map [key ]=artist
|
||||
self .all_fetched_artists =list (unique_artists_map .values ())
|
||||
|
||||
self .all_fetched_artists .sort (key =lambda x :x ['name'].lower ())
|
||||
self ._populate_artist_list_widget ()
|
||||
|
||||
if fetched_any_successfully and self .all_fetched_artists :
|
||||
self .status_label .setText (self ._tr ("fav_artists_found_status","Found {count} total favorite artist(s).").format (count =len (self .all_fetched_artists )))
|
||||
self ._show_content_elements (True )
|
||||
self .download_button .setEnabled (True )
|
||||
elif not fetched_any_successfully and not errors_occurred :
|
||||
self .status_label .setText (self ._tr ("fav_artists_none_found_status","No favorite artists found on Kemono.su or Coomer.su."))
|
||||
self ._show_content_elements (False )
|
||||
self .download_button .setEnabled (False )
|
||||
else :
|
||||
final_error_message =self ._tr ("fav_artists_failed_status","Failed to fetch favorites.")
|
||||
if errors_occurred :
|
||||
final_error_message +=" Errors: "+"; ".join (errors_occurred )
|
||||
self .status_label .setText (final_error_message )
|
||||
self ._show_content_elements (False )
|
||||
self .download_button .setEnabled (False )
|
||||
if fetched_any_successfully and not self .all_fetched_artists :
|
||||
self .status_label .setText (self ._tr ("fav_artists_no_favorites_after_processing","No favorite artists found after processing."))
|
||||
|
||||
def _populate_artist_list_widget (self ,artists_to_display =None ):
|
||||
self .artist_list_widget .clear ()
|
||||
source_list =artists_to_display if artists_to_display is not None else self .all_fetched_artists
|
||||
for artist_data in source_list :
|
||||
item =QListWidgetItem (f"{artist_data ['name']} ({artist_data .get ('service','N/A').capitalize ()})")
|
||||
item .setFlags (item .flags ()|Qt .ItemIsUserCheckable )
|
||||
item .setCheckState (Qt .Unchecked )
|
||||
item .setData (Qt .UserRole ,artist_data )
|
||||
self .artist_list_widget .addItem (item )
|
||||
|
||||
def _filter_artist_list_display (self ):
|
||||
search_text =self .search_input .text ().lower ().strip ()
|
||||
if not search_text :
|
||||
self ._populate_artist_list_widget ()
|
||||
return
|
||||
|
||||
filtered_artists =[
|
||||
artist for artist in self .all_fetched_artists
|
||||
if search_text in artist ['name'].lower ()or search_text in artist ['url'].lower ()
|
||||
]
|
||||
self ._populate_artist_list_widget (filtered_artists )
|
||||
|
||||
def _select_all_items (self ):
|
||||
for i in range (self .artist_list_widget .count ()):
|
||||
self .artist_list_widget .item (i ).setCheckState (Qt .Checked )
|
||||
|
||||
def _deselect_all_items (self ):
|
||||
for i in range (self .artist_list_widget .count ()):
|
||||
self .artist_list_widget .item (i ).setCheckState (Qt .Unchecked )
|
||||
|
||||
def _accept_selection_action (self ):
|
||||
self .selected_artists_data =[]
|
||||
for i in range (self .artist_list_widget .count ()):
|
||||
item =self .artist_list_widget .item (i )
|
||||
if item .checkState ()==Qt .Checked :
|
||||
self .selected_artists_data .append (item .data (Qt .UserRole ))
|
||||
|
||||
if not self .selected_artists_data :
|
||||
QMessageBox .information (self ,"No Selection","Please select at least one artist to download.")
|
||||
return
|
||||
self .accept ()
|
||||
|
||||
def get_selected_artists (self ):
|
||||
return self .selected_artists_data
|
||||
Reference in New Issue
Block a user