1919import logging
2020import asyncio
2121from ipaddress import ip_address
22+ import os
2223
2324
2425logger = get_logger (__name__ )
@@ -571,7 +572,7 @@ async def download_generator(
571572 min_file_size : int = 1024 , # Default 1KB minimum size
572573 max_retries : int = None , # Prevent infinite retry loops
573574 ** params : Unpack [CobaltRequestParams ],
574- ) -> AsyncGenerator [Path | Tuple [str , Optional [Path ], Optional [Exception ]], None ]:
575+ ) -> AsyncGenerator [Path | List [ Path ] | Tuple [str , Optional [Path | List [ Path ] ], Optional [Exception ]], None ]:
575576 """
576577 Download multiple files from Cobalt, yielding results as they complete.
577578
@@ -588,7 +589,7 @@ async def download_generator(
588589 Yields:
589590 Tuples of (url, file_path, exception) where:
590591 - url is the original URL requested
591- - file_path is the Path to the downloaded file (None if failed)
592+ - file_path is the Path to the downloaded file, or if the response was a picker, a list of Paths (None if failed)
592593 - exception is the exception that occurred (None if successful)
593594 """
594595 # Check if bulk download is allowed in config
@@ -695,8 +696,83 @@ async def process_url(url):
695696 logger .debug (f"Error remuxing file { file_path } : { e } " )
696697
697698 return url , file_path , None
699+ elif response .get ("status" , "" ) == "picker" :
700+ logger .debug (f"Picker response detected for { url } with { len (response .get ('picker' , []))} items" )
701+
702+ # Get the instance that responded
703+ responding_instance = response .get ("instance_info" , {}).get ("url" )
704+
705+ # Handle picker response - download all items
706+ picker_items = response .get ("picker" , [])
707+ downloaded_paths = []
708+ download_errors = []
709+
710+ # Helper function to truncate long filenames while preserving extension
711+ def safe_filename (url_str , max_length = 200 ):
712+ # Extract original filename from URL
713+ basename = os .path .basename (url_str .split ('?' )[0 ])
714+
715+ # Split into name and extension
716+ name , ext = os .path .splitext (basename )
717+
718+ # If filename is too long, truncate the name part
719+ if len (basename ) > max_length :
720+ # Make sure we leave enough room for the extension
721+ truncated_name = name [:max_length - len (ext ) - 1 ]
722+ return f"{ truncated_name } { ext } "
723+ return basename
724+
725+ # Download each picker item
726+ for idx , item in enumerate (picker_items ):
727+ item_url = item .get ("url" )
728+ if not item_url :
729+ continue
730+
731+ # Create a descriptive filename that's not too long
732+ item_type = item .get ("type" , "media" )
733+ # Generate a filename with an index to keep items in order
734+ raw_filename = safe_filename (item_url )
735+ item_filename = f"{ url .split ('/' )[- 1 ].split ('?' )[0 ][:30 ]} _{ item_type } _{ idx + 1 :02d} _{ raw_filename [- 40 :]} "
736+
737+ try :
738+ # Create a download task for this item
739+ download_task = await self .client .detached_download (
740+ url = item_url ,
741+ filename = item_filename ,
742+ timeout = self .config .get ("download_timeout" , 60 ),
743+ progressive_timeout = True ,
744+ )
745+
746+ file_path = await download_task
747+
748+ # Check if file is a "ghost file" (too small)
749+ if file_path and file_path .exists ():
750+ file_size = file_path .stat ().st_size
751+ if file_size < min_file_size :
752+ logger .warning (f"Ghost file detected for picker item { idx + 1 } : { file_path } ({ file_size } bytes)" )
753+ try :
754+ file_path .unlink ()
755+ except Exception as e :
756+ logger .debug (f"Failed to delete ghost file { file_path } : { e } " )
757+ continue
758+
759+ # Add to list of downloaded paths if successful
760+ if file_path :
761+ downloaded_paths .append (file_path )
762+ logger .debug (f"Downloaded picker item { idx + 1 } /{ len (picker_items )} : { file_path } " )
763+ except Exception as e :
764+ logger .debug (f"Failed to download picker item { idx + 1 } : { e } " )
765+ download_errors .append (e )
766+
767+ # If we downloaded at least one file, consider it a success
768+ if downloaded_paths :
769+ return url , downloaded_paths , None
770+ else :
771+ # If all downloads failed, return an error
772+ error_msg = f"Failed to download any files from picker response ({ len (download_errors )} errors)"
773+ return url , None , ValueError (error_msg )
698774 else :
699- # Handle picker responses or other status types
775+ # Handle other status types
700776 error = ValueError (f"Unsupported response status: { response .get ('status' )} " )
701777 return url , None , error
702778 except Exception as e :
0 commit comments