mirror of
https://github.com/Yuvi9587/Kemono-Downloader.git
synced 2025-12-29 16:14:44 +00:00
1038 lines
48 KiB
Python
1038 lines
48 KiB
Python
# --- Standard Library Imports ---
|
||
import json
|
||
import os
|
||
import sys
|
||
import threading
|
||
import time
|
||
import unicodedata
|
||
from collections import defaultdict
|
||
from urllib.parse import urlparse
|
||
|
||
# --- PyQt5 Imports ---
|
||
from PyQt5.QtCore import pyqtSignal, QCoreApplication, QSize, QThread, QTimer, Qt
|
||
from PyQt5.QtWidgets import (
|
||
QApplication, QDialog, QHBoxLayout, QLabel, QLineEdit, QListWidget,
|
||
QListWidgetItem, QMessageBox, QPushButton, QVBoxLayout, QAbstractItemView,
|
||
QSplitter, QProgressBar, QWidget, QFileDialog
|
||
)
|
||
|
||
# --- Local Application Imports ---
|
||
from ...i18n.translator import get_translation
|
||
from ..main_window import get_app_icon_object
|
||
from ...core.api_client import download_from_api
|
||
from ...utils.network_utils import extract_post_info, prepare_cookies_for_request
|
||
from ...utils.resolution import get_dark_theme
|
||
|
||
|
||
class PostsFetcherThread (QThread ):
|
||
status_update =pyqtSignal (str )
|
||
posts_fetched_signal =pyqtSignal (object ,list )
|
||
fetch_error_signal =pyqtSignal (object ,str )
|
||
finished_signal =pyqtSignal ()
|
||
|
||
def __init__ (self ,creators_to_fetch ,parent_dialog_ref ):
|
||
super ().__init__ ()
|
||
self .creators_to_fetch =creators_to_fetch
|
||
self .parent_dialog =parent_dialog_ref
|
||
self .cancellation_flag =threading .Event ()
|
||
|
||
def cancel (self ):
|
||
self .cancellation_flag .set ()
|
||
self .status_update .emit (self .parent_dialog ._tr ("post_fetch_cancelled_status","Post fetching cancellation requested..."))
|
||
|
||
def run (self ):
|
||
if not self .creators_to_fetch :
|
||
self .status_update .emit (self .parent_dialog ._tr ("no_creators_to_fetch_status","No creators selected to fetch posts for."))
|
||
self .finished_signal .emit ()
|
||
return
|
||
|
||
for creator_data in self .creators_to_fetch :
|
||
if self .cancellation_flag .is_set ():
|
||
break
|
||
|
||
creator_name =creator_data .get ('name','Unknown Creator')
|
||
service =creator_data .get ('service')
|
||
user_id =creator_data .get ('id')
|
||
|
||
print(f"[DEBUG] Fetching posts for: name={creator_name}, service={service}, user_id={user_id}")
|
||
|
||
if not service or not user_id :
|
||
self .fetch_error_signal .emit (creator_data ,f"Missing service or ID for {creator_name }")
|
||
continue
|
||
|
||
self .status_update .emit (self .parent_dialog ._tr ("fetching_posts_for_creator_status_all_pages","Fetching all posts for {creator_name} ({service})... This may take a while.").format (creator_name =creator_name ,service =service ))
|
||
|
||
domain =self .parent_dialog ._get_domain_for_service (service )
|
||
api_url_base =f"https://{domain }/api/v1/{service }/user/{user_id }"
|
||
print(f"[DEBUG] API URL: {api_url_base}")
|
||
|
||
|
||
use_cookie_param =False
|
||
cookie_text_param =""
|
||
selected_cookie_file_param =None
|
||
app_base_dir_param =None
|
||
|
||
if self .parent_dialog .parent_app :
|
||
app =self .parent_dialog .parent_app
|
||
use_cookie_param =app .use_cookie_checkbox .isChecked ()
|
||
cookie_text_param =app .cookie_text_input .text ().strip ()
|
||
selected_cookie_file_param =app .selected_cookie_filepath
|
||
app_base_dir_param =app .app_base_dir
|
||
|
||
all_posts_for_this_creator =[]
|
||
try :
|
||
post_generator =download_from_api (
|
||
api_url_base ,
|
||
logger =lambda msg :self .status_update .emit (f"[API Fetch - {creator_name }] {msg }"),
|
||
|
||
use_cookie =use_cookie_param ,
|
||
cookie_text =cookie_text_param ,
|
||
selected_cookie_file =selected_cookie_file_param ,
|
||
app_base_dir =app_base_dir_param ,
|
||
manga_filename_style_for_sort_check =None ,
|
||
cancellation_event =self .cancellation_flag
|
||
)
|
||
|
||
for posts_batch in post_generator :
|
||
if self .cancellation_flag .is_set ():
|
||
self .status_update .emit (f"Post fetching for {creator_name } cancelled during pagination.")
|
||
break
|
||
all_posts_for_this_creator .extend (posts_batch )
|
||
print(f"[DEBUG] Fetched batch: {len(posts_batch)} posts, total so far: {len(all_posts_for_this_creator)}")
|
||
self .status_update .emit (f"Fetched {len (all_posts_for_this_creator )} posts so far for {creator_name }...")
|
||
|
||
print(f"[DEBUG] Finished fetching for {creator_name}: {len(all_posts_for_this_creator)} posts total.")
|
||
if not self .cancellation_flag .is_set ():
|
||
self .posts_fetched_signal .emit (creator_data ,all_posts_for_this_creator )
|
||
self .status_update .emit (f"Finished fetching {len (all_posts_for_this_creator )} posts for {creator_name }.")
|
||
else :
|
||
self .posts_fetched_signal .emit (creator_data ,all_posts_for_this_creator )
|
||
self .status_update .emit (f"Fetching for {creator_name } cancelled. {len (all_posts_for_this_creator )} posts collected.")
|
||
|
||
except RuntimeError as e :
|
||
print(f"[DEBUG] RuntimeError for {creator_name}: {e}")
|
||
if "cancelled by user"in str (e ).lower ()or self .cancellation_flag .is_set ():
|
||
self .status_update .emit (f"Post fetching for {creator_name } cancelled: {e }")
|
||
self .posts_fetched_signal .emit (creator_data ,all_posts_for_this_creator )
|
||
else :
|
||
self .fetch_error_signal .emit (creator_data ,f"Runtime error fetching posts for {creator_name }: {e }")
|
||
except Exception as e :
|
||
print(f"[DEBUG] Exception for {creator_name}: {e}")
|
||
self .fetch_error_signal .emit (creator_data ,f"Error fetching posts for {creator_name }: {e }")
|
||
|
||
if self .cancellation_flag .is_set ():
|
||
break
|
||
QThread .msleep (200 )
|
||
|
||
if self .cancellation_flag .is_set ():
|
||
self .status_update .emit (self .parent_dialog ._tr ("post_fetch_cancelled_status_done","Post fetching cancelled."))
|
||
else :
|
||
self .status_update .emit (self .parent_dialog ._tr ("post_fetch_finished_status","Finished fetching posts for selected creators."))
|
||
self .finished_signal .emit ()
|
||
|
||
|
||
class EmptyPopupDialog (QDialog ):
|
||
"""A simple empty popup dialog."""
|
||
SCOPE_CHARACTERS ="Characters"
|
||
INITIAL_LOAD_LIMIT =200
|
||
SCOPE_CREATORS ="Creators"
|
||
|
||
|
||
def __init__ (self ,app_base_dir ,parent_app_ref ,parent =None ):
|
||
super ().__init__ (parent )
|
||
self.parent_app = parent_app_ref
|
||
|
||
scale_factor = getattr(self.parent_app, 'scale_factor', 1.0)
|
||
|
||
self.setMinimumSize(int(400 * scale_factor), int(300 * scale_factor))
|
||
self.current_scope_mode = self.SCOPE_CREATORS
|
||
self .app_base_dir =app_base_dir
|
||
|
||
app_icon =get_app_icon_object ()
|
||
if app_icon and not app_icon .isNull ():
|
||
self .setWindowIcon (app_icon )
|
||
self.update_profile_data = None
|
||
self.update_creator_name = None
|
||
self .selected_creators_for_queue =[]
|
||
self .globally_selected_creators ={}
|
||
self .fetched_posts_data ={}
|
||
self .post_fetch_thread =None
|
||
self .TITLE_COLUMN_WIDTH_FOR_POSTS =70
|
||
self .globally_selected_post_ids =set ()
|
||
self ._is_scrolling_titles =False
|
||
self ._is_scrolling_dates =False
|
||
|
||
|
||
dialog_layout =QHBoxLayout (self )
|
||
self .setLayout (dialog_layout )
|
||
|
||
|
||
|
||
self .left_pane_widget =QWidget ()
|
||
left_pane_layout =QVBoxLayout (self .left_pane_widget )
|
||
|
||
|
||
search_fetch_layout =QHBoxLayout ()
|
||
self .search_input =QLineEdit ()
|
||
self .search_input .textChanged .connect (self ._filter_list )
|
||
search_fetch_layout .addWidget (self .search_input ,1 )
|
||
self .fetch_posts_button =QPushButton ()
|
||
self .fetch_posts_button .setEnabled (False )
|
||
self .fetch_posts_button .clicked .connect (self ._handle_fetch_posts_click )
|
||
search_fetch_layout .addWidget (self .fetch_posts_button )
|
||
left_pane_layout .addLayout (search_fetch_layout )
|
||
|
||
self .progress_bar =QProgressBar ()
|
||
self .progress_bar .setRange (0 ,0 )
|
||
self .progress_bar .setTextVisible (False )
|
||
self .progress_bar .setVisible (False )
|
||
left_pane_layout .addWidget (self .progress_bar )
|
||
|
||
self .list_widget =QListWidget ()
|
||
self .list_widget .itemChanged .connect (self ._handle_item_check_changed )
|
||
left_pane_layout .addWidget (self .list_widget )
|
||
|
||
|
||
left_bottom_buttons_layout =QHBoxLayout ()
|
||
self .add_selected_button =QPushButton ()
|
||
self .add_selected_button .setToolTip (
|
||
"Add Selected Creators to URL Input\n\n"
|
||
"Adds the names of all checked creators to the main URL input field,\n"
|
||
"comma-separated, and closes this dialog."
|
||
)
|
||
self .add_selected_button .clicked .connect (self ._handle_add_selected )
|
||
self .add_selected_button .setDefault (True )
|
||
left_bottom_buttons_layout .addWidget (self .add_selected_button )
|
||
self .scope_button =QPushButton ()
|
||
self .scope_button .clicked .connect (self ._toggle_scope_mode )
|
||
left_bottom_buttons_layout .addWidget (self .scope_button )
|
||
left_pane_layout .addLayout (left_bottom_buttons_layout )
|
||
self.update_button = QPushButton()
|
||
self.update_button.clicked.connect(self._handle_update_check)
|
||
left_bottom_buttons_layout.addWidget(self.update_button)
|
||
|
||
|
||
self .right_pane_widget =QWidget ()
|
||
right_pane_layout =QVBoxLayout (self .right_pane_widget )
|
||
|
||
self .posts_area_title_label =QLabel ("Fetched Posts")
|
||
self .posts_area_title_label .setAlignment (Qt .AlignCenter )
|
||
right_pane_layout .addWidget (self .posts_area_title_label )
|
||
|
||
self .posts_search_input =QLineEdit ()
|
||
self .posts_search_input .setVisible (False )
|
||
|
||
self .posts_search_input .textChanged .connect (self ._filter_fetched_posts_list )
|
||
right_pane_layout .addWidget (self .posts_search_input )
|
||
|
||
|
||
posts_headers_layout =QHBoxLayout ()
|
||
self .posts_title_header_label =QLabel ()
|
||
self .posts_title_header_label .setStyleSheet ("font-weight: bold; padding-left: 20px;")
|
||
posts_headers_layout .addWidget (self .posts_title_header_label ,7 )
|
||
|
||
self .posts_date_header_label =QLabel ()
|
||
self .posts_date_header_label .setStyleSheet ("font-weight: bold;")
|
||
posts_headers_layout .addWidget (self .posts_date_header_label ,3 )
|
||
right_pane_layout .addLayout (posts_headers_layout )
|
||
|
||
|
||
|
||
self .posts_content_splitter =QSplitter (Qt .Horizontal )
|
||
|
||
self .posts_title_list_widget =QListWidget ()
|
||
self .posts_title_list_widget .itemChanged .connect (self ._handle_post_item_check_changed )
|
||
self .posts_title_list_widget .setAlternatingRowColors (True )
|
||
self .posts_content_splitter .addWidget (self .posts_title_list_widget )
|
||
|
||
self .posts_date_list_widget =QListWidget ()
|
||
self .posts_date_list_widget .setSelectionMode (QAbstractItemView .NoSelection )
|
||
self .posts_date_list_widget .setAlternatingRowColors (True )
|
||
self .posts_date_list_widget .setHorizontalScrollBarPolicy (Qt .ScrollBarAlwaysOff )
|
||
self .posts_content_splitter .addWidget (self .posts_date_list_widget )
|
||
|
||
right_pane_layout .addWidget (self .posts_content_splitter ,1 )
|
||
|
||
posts_buttons_top_layout =QHBoxLayout ()
|
||
self .posts_select_all_button =QPushButton ()
|
||
self .posts_select_all_button .clicked .connect (self ._handle_posts_select_all )
|
||
posts_buttons_top_layout .addWidget (self .posts_select_all_button )
|
||
|
||
self .posts_deselect_all_button =QPushButton ()
|
||
self .posts_deselect_all_button .clicked .connect (self ._handle_posts_deselect_all )
|
||
posts_buttons_top_layout .addWidget (self .posts_deselect_all_button )
|
||
right_pane_layout .addLayout (posts_buttons_top_layout )
|
||
|
||
posts_buttons_bottom_layout =QHBoxLayout ()
|
||
self .posts_add_selected_button =QPushButton ()
|
||
self .posts_add_selected_button .clicked .connect (self ._handle_posts_add_selected_to_queue )
|
||
posts_buttons_bottom_layout .addWidget (self .posts_add_selected_button )
|
||
|
||
self .posts_close_button =QPushButton ()
|
||
self .posts_close_button .clicked .connect (self ._handle_posts_close_view )
|
||
posts_buttons_bottom_layout .addWidget (self .posts_close_button )
|
||
right_pane_layout .addLayout (posts_buttons_bottom_layout )
|
||
|
||
self .right_pane_widget .hide ()
|
||
|
||
|
||
|
||
|
||
|
||
self .main_splitter =QSplitter (Qt .Horizontal )
|
||
self .main_splitter .addWidget (self .left_pane_widget )
|
||
self .main_splitter .addWidget (self .right_pane_widget )
|
||
self .main_splitter .setCollapsible (0 ,False )
|
||
self .main_splitter .setCollapsible (1 ,True )
|
||
|
||
|
||
self .posts_title_list_widget .verticalScrollBar ().valueChanged .connect (self ._sync_scroll_dates )
|
||
self .posts_date_list_widget .verticalScrollBar ().valueChanged .connect (self ._sync_scroll_titles )
|
||
dialog_layout .addWidget (self .main_splitter )
|
||
|
||
self .original_size =self .sizeHint ()
|
||
self .main_splitter .setSizes ([int (self .width ()*scale_factor ),0 ])
|
||
|
||
self ._retranslate_ui ()
|
||
|
||
if self.parent_app and self.parent_app.current_theme == "dark":
|
||
# Get the scale factor from the parent app
|
||
scale = getattr(self.parent_app, 'scale_factor', 1)
|
||
# Call the imported function with the correct scale
|
||
self.setStyleSheet(get_dark_theme(scale))
|
||
else:
|
||
# Explicitly set a blank stylesheet for light mode
|
||
self.setStyleSheet("")
|
||
|
||
self .resize (int ((self .original_size .width ()+50 )*scale_factor ),int ((self .original_size .height ()+100 )*scale_factor ))
|
||
|
||
QTimer .singleShot (0 ,self ._perform_initial_load )
|
||
|
||
def _center_on_screen (self ):
|
||
"""Centers the dialog on the parent's screen or the primary screen."""
|
||
if self .parent_app :
|
||
parent_rect =self .parent_app .frameGeometry ()
|
||
self .move (parent_rect .center ()-self .rect ().center ())
|
||
else :
|
||
try :
|
||
screen_geo =QApplication .primaryScreen ().availableGeometry ()
|
||
self .move (screen_geo .center ()-self .rect ().center ())
|
||
except AttributeError :
|
||
pass
|
||
|
||
def _handle_update_check(self):
|
||
"""Opens a dialog to select a creator profile and loads it for an update session."""
|
||
appdata_dir = os.path.join(self.app_base_dir, "appdata")
|
||
profiles_dir = os.path.join(appdata_dir, "creator_profiles")
|
||
|
||
if not os.path.isdir(profiles_dir):
|
||
QMessageBox.warning(self, "Directory Not Found", f"The creator profiles directory does not exist yet.\n\nPath: {profiles_dir}")
|
||
return
|
||
|
||
filepath, _ = QFileDialog.getOpenFileName(self, "Select Creator Profile for Update", profiles_dir, "JSON Files (*.json)")
|
||
|
||
if filepath:
|
||
try:
|
||
with open(filepath, 'r', encoding='utf-8') as f:
|
||
data = json.load(f)
|
||
|
||
if 'creator_url' not in data or 'processed_post_ids' not in data:
|
||
raise ValueError("Invalid profile format.")
|
||
|
||
self.update_profile_data = data
|
||
self.update_creator_name = os.path.basename(filepath).replace('.json', '')
|
||
self.accept() # Close the dialog and signal success
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Error Loading Profile", f"Could not load or parse the selected profile file:\n\n{e}")
|
||
|
||
def _handle_fetch_posts_click (self ):
|
||
selected_creators =list (self .globally_selected_creators .values ())
|
||
print(f"[DEBUG] Selected creators for fetch: {selected_creators}")
|
||
if not selected_creators :
|
||
QMessageBox .information (self ,self ._tr ("no_selection_title","No Selection"),
|
||
"Please select at least one creator to fetch posts for.")
|
||
return
|
||
|
||
if self .parent_app :
|
||
parent_geometry =self .parent_app .geometry ()
|
||
new_width =int (parent_geometry .width ()*0.75 )
|
||
new_height =int (parent_geometry .height ()*0.80 )
|
||
self .resize (new_width ,new_height )
|
||
self ._center_on_screen ()
|
||
|
||
self .right_pane_widget .show ()
|
||
QTimer .singleShot (10 ,lambda :self .main_splitter .setSizes ([int (self .width ()*0.3 ),int (self .width ()*0.7 )]))
|
||
|
||
QTimer .singleShot (20 ,lambda :self .posts_content_splitter .setSizes ([int (self .posts_content_splitter .width ()*0.7 ),int (self .posts_content_splitter .width ()*0.3 )]))
|
||
self .add_selected_button .setEnabled (False )
|
||
self .globally_selected_post_ids .clear ()
|
||
self .posts_search_input .setVisible (True )
|
||
self .setWindowTitle (self ._tr ("creator_popup_title_fetching","Creator Posts"))
|
||
|
||
self .fetch_posts_button .setEnabled (False )
|
||
self .posts_title_list_widget .clear ()
|
||
self .posts_date_list_widget .clear ()
|
||
self .fetched_posts_data .clear ()
|
||
self .posts_area_title_label .setText (self ._tr ("fav_posts_loading_status","Loading favorite posts..."))
|
||
self .posts_title_list_widget .itemChanged .connect (self ._handle_post_item_check_changed )
|
||
self .progress_bar .setVisible (True )
|
||
|
||
if self .post_fetch_thread and self .post_fetch_thread .isRunning ():
|
||
self .post_fetch_thread .cancel ()
|
||
self .post_fetch_thread .wait ()
|
||
print(f"[DEBUG] Starting PostsFetcherThread with creators: {selected_creators}")
|
||
self .post_fetch_thread =PostsFetcherThread (selected_creators ,self )
|
||
self .post_fetch_thread .status_update .connect (self ._handle_fetch_status_update )
|
||
self .post_fetch_thread .posts_fetched_signal .connect (self ._handle_posts_fetched )
|
||
self .post_fetch_thread .fetch_error_signal .connect (self ._handle_fetch_error )
|
||
self .post_fetch_thread .finished_signal .connect (self ._handle_fetch_finished )
|
||
self .post_fetch_thread .start ()
|
||
|
||
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 ("creator_popup_title","Creator Selection"))
|
||
self .search_input .setPlaceholderText (self ._tr ("creator_popup_search_placeholder","Search by name, service, or paste creator URL..."))
|
||
self .add_selected_button .setText (self ._tr ("creator_popup_add_selected_button","Add Selected"))
|
||
self .fetch_posts_button .setText (self ._tr ("fetch_posts_button_text","Fetch Posts"))
|
||
self ._update_scope_button_text_and_tooltip ()
|
||
self.update_button.setText(self._tr("check_for_updates_button", "Check for Updates"))
|
||
|
||
self .posts_search_input .setPlaceholderText (self ._tr ("creator_popup_posts_search_placeholder","Search fetched posts by title..."))
|
||
|
||
self .posts_title_header_label .setText (self ._tr ("column_header_post_title","Post Title"))
|
||
self .posts_date_header_label .setText (self ._tr ("column_header_date_uploaded","Date Uploaded"))
|
||
|
||
self .posts_area_title_label .setText (self ._tr ("creator_popup_posts_area_title","Fetched Posts"))
|
||
self .posts_select_all_button .setText (self ._tr ("select_all_button_text","Select All"))
|
||
self .posts_deselect_all_button .setText (self ._tr ("deselect_all_button_text","Deselect All"))
|
||
self .posts_add_selected_button .setText (self ._tr ("creator_popup_add_posts_to_queue_button","Add Selected Posts to Queue"))
|
||
self .posts_close_button .setText (self ._tr ("fav_posts_cancel_button","Cancel"))
|
||
|
||
def _sync_scroll_dates (self ,value ):
|
||
if not self ._is_scrolling_titles :
|
||
self ._is_scrolling_dates =True
|
||
self .posts_date_list_widget .verticalScrollBar ().setValue (value )
|
||
self ._is_scrolling_dates =False
|
||
|
||
def _sync_scroll_titles (self ,value ):
|
||
if not self ._is_scrolling_dates :
|
||
self ._is_scrolling_titles =True
|
||
self .posts_title_list_widget .verticalScrollBar ().setValue (value )
|
||
self ._is_scrolling_titles =False
|
||
|
||
def _perform_initial_load (self ):
|
||
"""Called by QTimer to load data after dialog is shown."""
|
||
self ._load_creators_from_json ()
|
||
|
||
|
||
|
||
def _load_creators_from_json (self ):
|
||
"""Loads creators from creators.json and populates the list widget."""
|
||
self .list_widget .clear ()
|
||
|
||
self .progress_bar .setVisible (True )
|
||
QCoreApplication .processEvents ()
|
||
if not self .isVisible ():return
|
||
# Always resolve project root relative to this file
|
||
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../"))
|
||
creators_file_path = os.path.join(project_root, "data", "creators.json")
|
||
|
||
if not os .path .exists (creators_file_path ):
|
||
self .list_widget .addItem (f"Error: creators.json not found at {creators_file_path }")
|
||
self .all_creators_data =[]
|
||
self .progress_bar .setVisible (False )
|
||
QCoreApplication .processEvents ()
|
||
return
|
||
|
||
try :
|
||
with open (creators_file_path ,'r',encoding ='utf-8')as f :
|
||
data =json .load (f )
|
||
QCoreApplication .processEvents ()
|
||
if not self .isVisible ():return
|
||
|
||
raw_data_list =[]
|
||
if isinstance (data ,list )and len (data )>0 and isinstance (data [0 ],list ):
|
||
raw_data_list =data [0 ]
|
||
elif isinstance (data ,list )and all (isinstance (item ,dict )for item in data ):
|
||
raw_data_list =data
|
||
else :
|
||
self .list_widget .addItem ("Error: Invalid format in creators.json.")
|
||
self .all_creators_data =[]
|
||
self .progress_bar .setVisible (False );QCoreApplication .processEvents ();return
|
||
|
||
|
||
unique_creators_map ={}
|
||
duplicates_skipped_count =0
|
||
for creator_entry in raw_data_list :
|
||
service =creator_entry .get ('service')
|
||
creator_id =creator_entry .get ('id')
|
||
name_for_log =creator_entry .get ('name','Unknown')
|
||
|
||
if service and creator_id :
|
||
key =(str (service ).lower ().strip (),str (creator_id ).strip ())
|
||
if key not in unique_creators_map :
|
||
unique_creators_map [key ]=creator_entry
|
||
else :
|
||
duplicates_skipped_count +=1
|
||
else :
|
||
print (f"Warning: Creator entry in creators.json missing service or ID: {name_for_log }")
|
||
|
||
self .all_creators_data =list (unique_creators_map .values ())
|
||
if duplicates_skipped_count >0 :
|
||
print (f"INFO (Creator Popup): Skipped {duplicates_skipped_count } duplicate creator entries from creators.json based on (service, id).")
|
||
|
||
self .all_creators_data .sort (key =lambda c :(-c .get ('favorited',0 ),c .get ('name','').lower ()))
|
||
QCoreApplication .processEvents ()
|
||
if not self .isVisible ():return
|
||
|
||
except json .JSONDecodeError :
|
||
self .list_widget .addItem ("Error: Could not parse creators.json.")
|
||
self .all_creators_data =[]
|
||
self .progress_bar .setVisible (False );QCoreApplication .processEvents ();return
|
||
except Exception as e :
|
||
self .list_widget .addItem (f"Error loading creators: {e }")
|
||
self .all_creators_data =[]
|
||
self .progress_bar .setVisible (False );QCoreApplication .processEvents ();return
|
||
|
||
self ._filter_list ()
|
||
|
||
def _populate_list_widget (self ,creators_to_display ):
|
||
"""Clears and populates the list widget with the given creator data."""
|
||
self .list_widget .blockSignals (True )
|
||
self .list_widget .clear ()
|
||
|
||
if not creators_to_display :
|
||
self .list_widget .blockSignals (False )
|
||
|
||
|
||
|
||
|
||
|
||
|
||
return
|
||
|
||
CHUNK_SIZE_POPULATE =100
|
||
for i in range (0 ,len (creators_to_display ),CHUNK_SIZE_POPULATE ):
|
||
if not self .isVisible ():
|
||
self .list_widget .blockSignals (False )
|
||
return
|
||
|
||
chunk =creators_to_display [i :i +CHUNK_SIZE_POPULATE ]
|
||
for creator in chunk :
|
||
creator_name_raw =creator .get ('name')
|
||
display_creator_name =creator_name_raw .strip ()if isinstance (creator_name_raw ,str )and creator_name_raw .strip ()else "Unknown Creator"
|
||
service_display_name =creator .get ('service','N/A').capitalize ()
|
||
display_text =f"{display_creator_name } ({service_display_name })"
|
||
item =QListWidgetItem (display_text )
|
||
item .setFlags (item .flags ()|Qt .ItemIsUserCheckable )
|
||
item .setData (Qt .UserRole ,creator )
|
||
service =creator .get ('service')
|
||
creator_id =creator .get ('id')
|
||
if service is not None and creator_id is not None :
|
||
unique_key =(str (service ),str (creator_id ))
|
||
if unique_key in self .globally_selected_creators :
|
||
item .setCheckState (Qt .Checked )
|
||
else :
|
||
item .setCheckState (Qt .Unchecked )
|
||
else :
|
||
item .setCheckState (Qt .Unchecked )
|
||
self .list_widget .addItem (item )
|
||
|
||
QCoreApplication .processEvents ()
|
||
|
||
self .list_widget .blockSignals (False )
|
||
|
||
def _filter_list (self ):
|
||
"""Filters the list widget based on the search input."""
|
||
raw_search_input =self .search_input .text ()
|
||
|
||
|
||
|
||
check_search_text_for_empty =raw_search_input .lower ().strip ()
|
||
|
||
QCoreApplication .processEvents ()
|
||
if not self .isVisible ():return
|
||
|
||
if not check_search_text_for_empty :
|
||
self .progress_bar .setVisible (False )
|
||
creators_to_show =self .all_creators_data [:self .INITIAL_LOAD_LIMIT ]
|
||
self ._populate_list_widget (creators_to_show )
|
||
self .search_input .setToolTip ("Search by name, service, or paste creator URL...")
|
||
QCoreApplication .processEvents ()
|
||
else :
|
||
self .progress_bar .setVisible (True )
|
||
QCoreApplication .processEvents ()
|
||
if not self .isVisible ():return
|
||
|
||
norm_search_casefolded =unicodedata .normalize ('NFKC',raw_search_input ).casefold ().strip ()
|
||
|
||
scored_matches =[]
|
||
|
||
parsed_service_from_url ,parsed_user_id_from_url ,_ =extract_post_info (raw_search_input )
|
||
|
||
if parsed_service_from_url and parsed_user_id_from_url :
|
||
|
||
self .search_input .setToolTip (f"Searching for URL: {raw_search_input [:50 ]}...")
|
||
for creator_data in self .all_creators_data :
|
||
creator_service_lower =creator_data .get ('service','').lower ()
|
||
|
||
creator_id_str_lower =str (creator_data .get ('id','')).lower ()
|
||
|
||
if creator_service_lower ==parsed_service_from_url .lower ()and creator_id_str_lower ==parsed_user_id_from_url .lower ():
|
||
scored_matches .append ((5 ,creator_data ))
|
||
|
||
break
|
||
|
||
else :
|
||
|
||
|
||
self .search_input .setToolTip ("Searching by name or service...")
|
||
norm_search_casefolded =unicodedata .normalize ('NFKC',raw_search_input ).casefold ().strip ()
|
||
|
||
CHUNK_SIZE_FILTER =500
|
||
for i in range (0 ,len (self .all_creators_data ),CHUNK_SIZE_FILTER ):
|
||
if not self .isVisible ():break
|
||
chunk =self .all_creators_data [i :i +CHUNK_SIZE_FILTER ]
|
||
for creator_data in chunk :
|
||
creator_name_raw =creator_data .get ('name','')
|
||
creator_service_raw =creator_data .get ('service','')
|
||
|
||
|
||
norm_creator_name_casefolded =unicodedata .normalize ('NFKC',creator_name_raw ).casefold ()
|
||
norm_service_casefolded =unicodedata .normalize ('NFKC',creator_service_raw ).casefold ()
|
||
|
||
current_score =0
|
||
|
||
if norm_search_casefolded ==norm_creator_name_casefolded :
|
||
current_score =4
|
||
|
||
elif norm_creator_name_casefolded .startswith (norm_search_casefolded ):
|
||
current_score =3
|
||
|
||
elif norm_search_casefolded in norm_creator_name_casefolded :
|
||
current_score =2
|
||
|
||
elif norm_search_casefolded in norm_service_casefolded :
|
||
current_score =1
|
||
|
||
if current_score >0 :
|
||
scored_matches .append ((current_score ,creator_data ))
|
||
|
||
QCoreApplication .processEvents ()
|
||
|
||
|
||
|
||
scored_matches .sort (key =lambda x :(-x [0 ],unicodedata .normalize ('NFKC',x [1 ].get ('name','')).casefold ()))
|
||
|
||
|
||
final_creators_to_display =[creator_data for score ,creator_data in scored_matches [:20 ]]
|
||
|
||
self ._populate_list_widget (final_creators_to_display )
|
||
self .progress_bar .setVisible (False )
|
||
|
||
|
||
if parsed_service_from_url and parsed_user_id_from_url :
|
||
if final_creators_to_display :
|
||
self .search_input .setToolTip (f"Found creator by URL: {final_creators_to_display [0 ].get ('name')}")
|
||
else :
|
||
self .search_input .setToolTip (f"URL parsed, but no matching creator found in your creators.json.")
|
||
else :
|
||
if final_creators_to_display :
|
||
self .search_input .setToolTip (f"Showing top {len (final_creators_to_display )} match(es) for '{raw_search_input [:30 ]}...'")
|
||
else :
|
||
self .search_input .setToolTip (f"No matches found for '{raw_search_input [:30 ]}...'")
|
||
|
||
def _toggle_scope_mode (self ):
|
||
"""Toggles the scope mode and updates the button text."""
|
||
if self .current_scope_mode ==self .SCOPE_CHARACTERS :
|
||
self .current_scope_mode =self .SCOPE_CREATORS
|
||
else :
|
||
self .current_scope_mode =self .SCOPE_CHARACTERS
|
||
self ._update_scope_button_text_and_tooltip ()
|
||
|
||
def _update_scope_button_text_and_tooltip (self ):
|
||
if self .current_scope_mode ==self .SCOPE_CHARACTERS :
|
||
self .scope_button .setText (self ._tr ("creator_popup_scope_characters_button","Scope: Characters"))
|
||
else :
|
||
self .scope_button .setText (self ._tr ("creator_popup_scope_creators_button","Scope: Creators"))
|
||
|
||
self .scope_button .setToolTip (
|
||
f"Current Download Scope: {self .current_scope_mode }\n\n"
|
||
f"Click to toggle between '{self .SCOPE_CHARACTERS }' and '{self .SCOPE_CREATORS }' scopes.\n"
|
||
f"'{self .SCOPE_CHARACTERS }': Downloads into character-named folders directly in the main Download Location (artists mixed).\n"
|
||
f"'{self .SCOPE_CREATORS }': Downloads into artist-named subfolders within the main Download Location, then character folders inside those.")
|
||
|
||
def _handle_fetch_status_update (self ,message ):
|
||
if self .parent_app :
|
||
self .parent_app .log_signal .emit (f"[CreatorPopup Fetch] {message }")
|
||
self .posts_area_title_label .setText (message )
|
||
|
||
def _handle_posts_fetched (self ,creator_info ,posts_list ):
|
||
creator_key =(creator_info .get ('service'),str (creator_info .get ('id')))
|
||
|
||
self .fetched_posts_data [creator_key ]=(creator_info ,posts_list )
|
||
self ._filter_fetched_posts_list ()
|
||
|
||
def _filter_fetched_posts_list (self ):
|
||
search_text =self .posts_search_input .text ().lower ().strip ()
|
||
|
||
data_for_rebuild ={}
|
||
|
||
if not self .fetched_posts_data :
|
||
self .posts_area_title_label .setText (self ._tr ("no_posts_fetched_yet_status","No posts fetched yet."))
|
||
elif not search_text :
|
||
data_for_rebuild =self .fetched_posts_data
|
||
|
||
total_posts_in_view =sum (len (posts_tuple [1 ])for posts_tuple in data_for_rebuild .values ())
|
||
if total_posts_in_view >0 :
|
||
self .posts_area_title_label .setText (self ._tr ("fetched_posts_count_label","Fetched {count} post(s). Select to add to queue.").format (count =total_posts_in_view ))
|
||
else :
|
||
self .posts_area_title_label .setText (self ._tr ("no_posts_found_for_selection","No posts found for selected creator(s)."))
|
||
else :
|
||
for creator_key ,(creator_data_tuple_part ,posts_list_tuple_part )in self .fetched_posts_data .items ():
|
||
matching_posts_for_creator =[
|
||
post for post in posts_list_tuple_part
|
||
if search_text in post .get ('title','').lower ()
|
||
]
|
||
if matching_posts_for_creator :
|
||
|
||
data_for_rebuild [creator_key ]=(creator_data_tuple_part ,matching_posts_for_creator )
|
||
|
||
|
||
total_matching_posts =sum (len (posts_tuple [1 ])for posts_tuple in data_for_rebuild .values ())
|
||
if total_matching_posts >0 :
|
||
self .posts_area_title_label .setText (self ._tr ("fetched_posts_count_label_filtered","Displaying {count} post(s) matching filter.").format (count =total_matching_posts ))
|
||
else :
|
||
self .posts_area_title_label .setText (self ._tr ("no_posts_match_search_filter","No posts match your search filter."))
|
||
|
||
self ._rebuild_posts_list_widget (filtered_data_map =data_for_rebuild )
|
||
|
||
def _rebuild_posts_list_widget (self ,filtered_data_map ):
|
||
self .posts_title_list_widget .blockSignals (True )
|
||
self .posts_date_list_widget .blockSignals (True )
|
||
self .posts_title_list_widget .clear ()
|
||
self .posts_date_list_widget .clear ()
|
||
data_to_display =filtered_data_map
|
||
|
||
if not data_to_display :
|
||
self .posts_title_list_widget .blockSignals (False )
|
||
self .posts_date_list_widget .blockSignals (False )
|
||
return
|
||
|
||
|
||
sorted_creator_keys =sorted (
|
||
data_to_display .keys (),
|
||
key =lambda k :data_to_display [k ][0 ].get ('name','').lower ()
|
||
)
|
||
|
||
total_posts_shown =0
|
||
for creator_key in sorted_creator_keys :
|
||
|
||
creator_info_original ,posts_for_this_creator =data_to_display .get (creator_key ,(None ,[]))
|
||
|
||
if not creator_info_original or not posts_for_this_creator :
|
||
continue
|
||
|
||
creator_header_item =QListWidgetItem (f"--- {self ._tr ('posts_for_creator_header','Posts for')} {creator_info_original ['name']} ({creator_info_original ['service']}) ---")
|
||
font =creator_header_item .font ()
|
||
font .setBold (True )
|
||
creator_header_item .setFont (font )
|
||
creator_header_item .setFlags (Qt .NoItemFlags )
|
||
self .posts_title_list_widget .addItem (creator_header_item )
|
||
self .posts_date_list_widget .addItem (QListWidgetItem (""))
|
||
|
||
for post in posts_for_this_creator :
|
||
post_title =post .get ('title',self ._tr ('untitled_post_placeholder','Untitled Post'))
|
||
|
||
|
||
date_prefix_str ="[No Date]"
|
||
published_date_str =post .get ('published')
|
||
added_date_str =post .get ('added')
|
||
|
||
date_to_use_str =None
|
||
if published_date_str :
|
||
date_to_use_str =published_date_str
|
||
elif added_date_str :
|
||
date_to_use_str =added_date_str
|
||
|
||
if date_to_use_str :
|
||
try :
|
||
|
||
formatted_date =date_to_use_str .split ('T')[0 ]
|
||
date_prefix_str =f"[{formatted_date }]"
|
||
except Exception :
|
||
pass
|
||
|
||
|
||
date_display_str ="[No Date]"
|
||
published_date_str =post .get ('published')
|
||
added_date_str =post .get ('added')
|
||
|
||
date_to_use_str =None
|
||
if published_date_str :
|
||
date_to_use_str =published_date_str
|
||
elif added_date_str :
|
||
date_to_use_str =added_date_str
|
||
|
||
if date_to_use_str :
|
||
try :
|
||
|
||
formatted_date =date_to_use_str .split ('T')[0 ]
|
||
date_display_str =f"[{formatted_date }]"
|
||
except Exception :
|
||
pass
|
||
|
||
|
||
title_item_text =f" {post_title }"
|
||
item =QListWidgetItem (title_item_text )
|
||
item .setFlags (item .flags ()|Qt .ItemIsUserCheckable )
|
||
item .setCheckState (Qt .Unchecked )
|
||
item_data ={
|
||
'title':post_title ,
|
||
'id':post .get ('id'),
|
||
'service':creator_info_original ['service'],
|
||
'user_id':creator_info_original ['id'],
|
||
'creator_name':creator_info_original ['name'],
|
||
'full_post_data':post ,
|
||
'date_display_str':date_display_str ,
|
||
'published_date_for_sort':date_to_use_str
|
||
}
|
||
item .setData (Qt .UserRole ,item_data )
|
||
post_unique_key =(
|
||
item_data ['service'],
|
||
str (item_data ['user_id']),
|
||
str (item_data ['id'])
|
||
)
|
||
if post_unique_key in self .globally_selected_post_ids :
|
||
item .setCheckState (Qt .Checked )
|
||
else :
|
||
item .setCheckState (Qt .Unchecked )
|
||
|
||
self .posts_title_list_widget .addItem (item )
|
||
total_posts_shown +=1
|
||
|
||
date_item =QListWidgetItem (f" {date_display_str }")
|
||
date_item .setFlags (Qt .NoItemFlags )
|
||
self .posts_date_list_widget .addItem (date_item )
|
||
|
||
self .posts_title_list_widget .blockSignals (False )
|
||
self .posts_date_list_widget .blockSignals (False )
|
||
|
||
def _handle_fetch_error (self ,creator_info ,error_message ):
|
||
creator_name =creator_info .get ('name','Unknown Creator')
|
||
if self .parent_app :
|
||
self .parent_app .log_signal .emit (f"[CreatorPopup Fetch ERROR] For {creator_name }: {error_message }")
|
||
|
||
self .posts_area_title_label .setText (self ._tr ("fetch_error_for_creator_label","Error fetching for {creator_name}").format (creator_name =creator_name ))
|
||
|
||
|
||
def _handle_fetch_finished (self ):
|
||
self .fetch_posts_button .setEnabled (True )
|
||
self .progress_bar .setVisible (False )
|
||
|
||
if not self .fetched_posts_data :
|
||
if self .post_fetch_thread and self .post_fetch_thread .cancellation_flag .is_set ():
|
||
self .posts_area_title_label .setText (self ._tr ("post_fetch_cancelled_status_done","Post fetching cancelled."))
|
||
else :
|
||
self .posts_area_title_label .setText (self ._tr ("failed_to_fetch_or_no_posts_label","Failed to fetch posts or no posts found."))
|
||
self .posts_search_input .setVisible (False )
|
||
elif not self .posts_title_list_widget .count ()and not self .posts_search_input .text ().strip ():
|
||
self .posts_area_title_label .setText (self ._tr ("no_posts_found_for_selection","No posts found for selected creator(s)."))
|
||
self .posts_search_input .setVisible (True )
|
||
else :
|
||
QTimer .singleShot (10 ,lambda :self .posts_content_splitter .setSizes ([int (self .posts_content_splitter .width ()*0.7 ),int (self .posts_content_splitter .width ()*0.3 )]))
|
||
self .posts_search_input .setVisible (True )
|
||
|
||
def _handle_posts_select_all (self ):
|
||
self .posts_title_list_widget .blockSignals (True )
|
||
for i in range (self .posts_title_list_widget .count ()):
|
||
item =self .posts_title_list_widget .item (i )
|
||
if item .flags ()&Qt .ItemIsUserCheckable :
|
||
item .setCheckState (Qt .Checked )
|
||
|
||
|
||
item_data =item .data (Qt .UserRole )
|
||
if item_data :
|
||
post_unique_key =(
|
||
item_data ['service'],
|
||
str (item_data ['user_id']),
|
||
str (item_data ['id'])
|
||
)
|
||
self .globally_selected_post_ids .add (post_unique_key )
|
||
self .posts_title_list_widget .blockSignals (False )
|
||
|
||
def _handle_posts_deselect_all (self ):
|
||
self .posts_title_list_widget .blockSignals (True )
|
||
for i in range (self .posts_title_list_widget .count ()):
|
||
item =self .posts_title_list_widget .item (i )
|
||
if item .flags ()&Qt .ItemIsUserCheckable :
|
||
item .setCheckState (Qt .Unchecked )
|
||
self .globally_selected_post_ids .clear ()
|
||
self .posts_title_list_widget .blockSignals (False )
|
||
|
||
def _handle_post_item_check_changed (self ,item ):
|
||
if not item or not item .data (Qt .UserRole ):
|
||
return
|
||
|
||
item_data =item .data (Qt .UserRole )
|
||
post_unique_key =(
|
||
item_data ['service'],
|
||
str (item_data ['user_id']),
|
||
str (item_data ['id'])
|
||
)
|
||
|
||
if item .checkState ()==Qt .Checked :
|
||
self .globally_selected_post_ids .add (post_unique_key )
|
||
else :
|
||
self .globally_selected_post_ids .discard (post_unique_key )
|
||
|
||
def _handle_posts_add_selected_to_queue (self ):
|
||
selected_posts_for_queue =[]
|
||
if not self .globally_selected_post_ids :
|
||
QMessageBox .information (self ,self ._tr ("no_selection_title","No Selection"),
|
||
self ._tr ("select_posts_to_queue_message","Please select at least one post to add to the queue."))
|
||
return
|
||
|
||
for post_key in self .globally_selected_post_ids :
|
||
service ,user_id_str ,post_id_str =post_key
|
||
post_data_found =None
|
||
creator_key_for_fetched_data =(service ,user_id_str )
|
||
|
||
|
||
if creator_key_for_fetched_data in self .fetched_posts_data :
|
||
_unused_creator_info ,posts_in_list_for_creator =self .fetched_posts_data [creator_key_for_fetched_data ]
|
||
for post_in_list in posts_in_list_for_creator :
|
||
if str (post_in_list .get ('id'))==post_id_str :
|
||
post_data_found =post_in_list
|
||
break
|
||
|
||
if post_data_found :
|
||
|
||
creator_info_original ,_unused_posts =self .fetched_posts_data .get (creator_key_for_fetched_data ,({},[]))
|
||
creator_name =creator_info_original .get ('name','Unknown Creator')if creator_info_original else 'Unknown Creator'
|
||
|
||
domain =self ._get_domain_for_service (service )
|
||
post_url =f"https://{domain }/{service }/user/{user_id_str }/post/{post_id_str }"
|
||
queue_item ={
|
||
'type':'single_post_from_popup',
|
||
'url':post_url ,
|
||
'name':post_data_found .get ('title',self ._tr ('untitled_post_placeholder','Untitled Post')),
|
||
'name_for_folder':creator_name ,
|
||
'service':service ,
|
||
'user_id':user_id_str ,
|
||
'post_id':post_id_str
|
||
}
|
||
selected_posts_for_queue .append (queue_item )
|
||
else :
|
||
|
||
|
||
|
||
|
||
if self .parent_app and hasattr (self .parent_app ,'log_signal'):
|
||
self .parent_app .log_signal .emit (f"⚠️ Could not find full post data for selected key: {post_key } when adding to queue.")
|
||
|
||
else :
|
||
domain =self ._get_domain_for_service (service )
|
||
post_url =f"https://{domain }/{service }/user/{user_id_str }/post/{post_id_str }"
|
||
queue_item ={
|
||
'type':'single_post_from_popup',
|
||
'url':post_url ,
|
||
'name':f"post id {post_id_str }",
|
||
'name_for_folder':user_id_str ,
|
||
'service':service ,
|
||
'user_id':user_id_str ,
|
||
'post_id':post_id_str
|
||
}
|
||
selected_posts_for_queue .append (queue_item )
|
||
|
||
if selected_posts_for_queue :
|
||
if self .parent_app and hasattr (self .parent_app ,'favorite_download_queue'):
|
||
for qi in selected_posts_for_queue :
|
||
self .parent_app .favorite_download_queue .append (qi )
|
||
|
||
num_just_added_posts =len (selected_posts_for_queue )
|
||
total_in_queue =len (self .parent_app .favorite_download_queue )
|
||
|
||
self .parent_app .log_signal .emit (f"ℹ️ Added {num_just_added_posts } selected posts to the download queue. Total in queue: {total_in_queue }.")
|
||
|
||
# --- START: MODIFIED LOGIC ---
|
||
# Removed the blockSignals(True/False) calls to allow the main window's UI to update correctly.
|
||
if self .parent_app .link_input :
|
||
self .parent_app .link_input .setText (
|
||
self .parent_app ._tr ("popup_posts_selected_text","Posts - {count} selected").format (count =num_just_added_posts )
|
||
)
|
||
self .parent_app .link_input .setPlaceholderText (
|
||
self .parent_app ._tr ("items_in_queue_placeholder","{count} items in queue from popup.").format (count =total_in_queue )
|
||
)
|
||
# --- END: MODIFIED LOGIC ---
|
||
|
||
self.selected_creators_for_queue.clear()
|
||
|
||
self .accept ()
|
||
else :
|
||
QMessageBox .information (self ,self ._tr ("no_selection_title","No Selection"),
|
||
self ._tr ("select_posts_to_queue_message","Please select at least one post to add to the queue."))
|
||
|
||
def _handle_posts_close_view (self ):
|
||
self .right_pane_widget .hide ()
|
||
self .main_splitter .setSizes ([self .width (),0 ])
|
||
self .posts_list_widget .itemChanged .disconnect (self ._handle_post_item_check_changed )
|
||
if hasattr (self ,'_handle_post_item_check_changed'):
|
||
self .posts_title_list_widget .itemChanged .disconnect (self ._handle_post_item_check_changed )
|
||
self .posts_search_input .setVisible (False )
|
||
self .posts_search_input .clear ()
|
||
self .globally_selected_post_ids .clear ()
|
||
self .add_selected_button .setEnabled (True )
|
||
self .setWindowTitle (self ._tr ("creator_popup_title","Creator Selection"))
|
||
|
||
def _get_domain_for_service (self ,service_name ):
|
||
"""Determines the base domain for a given service."""
|
||
service_lower =service_name .lower ()
|
||
if service_lower in ['onlyfans','fansly']:
|
||
return "coomer.su"
|
||
return "kemono.su"
|
||
|
||
def _handle_add_selected (self ):
|
||
"""Gathers globally selected creators and processes them."""
|
||
selected_display_names =[]
|
||
self .selected_creators_for_queue .clear ()
|
||
for creator_data in self .globally_selected_creators .values ():
|
||
creator_name =creator_data .get ('name')
|
||
self .selected_creators_for_queue .append (creator_data )
|
||
if creator_name :
|
||
selected_display_names .append (creator_name )
|
||
|
||
if selected_display_names :
|
||
main_app_window =self .parent ()
|
||
if hasattr (main_app_window ,'link_input'):
|
||
main_app_window .link_input .setText (", ".join (selected_display_names ))
|
||
self .accept ()
|
||
else :
|
||
QMessageBox .information (self ,"No Selection","No creators selected to add.")
|
||
|
||
def _handle_item_check_changed (self ,item ):
|
||
"""Updates the globally_selected_creators dict when an item's check state changes."""
|
||
creator_data =item .data (Qt .UserRole )
|
||
if not isinstance (creator_data ,dict ):
|
||
return
|
||
|
||
service =creator_data .get ('service')
|
||
creator_id =creator_data .get ('id')
|
||
|
||
if service is None or creator_id is None :
|
||
print (f"Warning: Creator data in list item missing service or id: {creator_data .get ('name')}")
|
||
return
|
||
|
||
unique_key =(str (service ),str (creator_id ))
|
||
|
||
if item .checkState ()==Qt .Checked :
|
||
self .globally_selected_creators [unique_key ]=creator_data
|
||
else :
|
||
if unique_key in self .globally_selected_creators :
|
||
del self .globally_selected_creators [unique_key ]
|
||
self .fetch_posts_button .setEnabled (bool (self .globally_selected_creators )) |