@@ -41,6 +41,18 @@ def get_json(bundle_path: str, object_id: Optional[str] = None) -> Dict:
4141 return json .loads (output or "{}" )
4242
4343
44+ def extract_id (ref : object ) -> Optional [str ]:
45+ if isinstance (ref , str ):
46+ return ref
47+ if isinstance (ref , dict ):
48+ if "id" in ref :
49+ return extract_id (ref ["id" ])
50+ if "_value" in ref :
51+ value = ref ["_value" ]
52+ return value if isinstance (value , str ) else None
53+ return None
54+
55+
4456def collect_nodes (node ) -> Tuple [List [Dict ], List [str ]]:
4557 attachments : List [Dict ] = []
4658 refs : List [str ] = []
@@ -55,13 +67,13 @@ def collect_nodes(node) -> Tuple[List[Dict], List[str]]:
5567 attachments .append (item )
5668 for key , value in current .items ():
5769 if key .endswith ("Ref" ) and isinstance (value , dict ):
58- ref_id = value . get ( "id" )
70+ ref_id = extract_id ( value )
5971 if isinstance (ref_id , str ) and ref_id :
6072 refs .append (ref_id )
6173 elif key .endswith ("Refs" ) and isinstance (value , dict ):
6274 for entry in value .get ("_values" , []):
6375 if isinstance (entry , dict ):
64- ref_id = entry . get ( "id" )
76+ ref_id = extract_id ( entry )
6577 if isinstance (ref_id , str ) and ref_id :
6678 refs .append (ref_id )
6779 if isinstance (value , (dict , list )):
@@ -71,14 +83,41 @@ def collect_nodes(node) -> Tuple[List[Dict], List[str]]:
7183 return attachments , refs
7284
7385
74- def is_image_attachment (attachment : Dict ) -> bool :
75- uti = (attachment .get ("uniformTypeIdentifier" ) or "" ).lower ()
76- filename = (attachment .get ("filename" ) or attachment .get ("name" ) or "" ).lower ()
77- if filename .endswith ((".png" , ".jpg" , ".jpeg" )):
86+ def _looks_like_image (value : Optional [str ]) -> bool :
87+ if not value :
88+ return False
89+ lower = value .lower ()
90+ if lower .endswith ((".png" , ".jpg" , ".jpeg" )):
7891 return True
79- if "png" in uti or "jpeg" in uti :
92+ keywords = ("png" , "jpeg" , "image" , "screenshot" )
93+ return any (keyword in lower for keyword in keywords )
94+
95+
96+ def is_image_attachment (attachment : Dict ) -> bool :
97+ filename = attachment .get ("filename" ) or attachment .get ("name" )
98+ if _looks_like_image (filename ):
8099 return True
81- return False
100+
101+ uti_candidates = [
102+ attachment .get ("uniformTypeIdentifier" ),
103+ attachment .get ("contentType" ),
104+ attachment .get ("uti" ),
105+ ]
106+ payload = (
107+ attachment .get ("payloadRef" )
108+ or attachment .get ("inlinePayloadRef" )
109+ or attachment .get ("payload" )
110+ )
111+ if isinstance (payload , dict ):
112+ uti_candidates .extend (
113+ [
114+ payload .get ("contentType" ),
115+ payload .get ("uti" ),
116+ payload .get ("uniformTypeIdentifier" ),
117+ ]
118+ )
119+
120+ return any (_looks_like_image (candidate ) for candidate in uti_candidates )
82121
83122
84123def sanitize_filename (name : str ) -> str :
@@ -101,8 +140,13 @@ def ensure_extension(name: str, uti: str) -> str:
101140def export_attachment (
102141 bundle_path : str , attachment : Dict , destination_dir : str , used_names : Set [str ]
103142) -> None :
104- payload = attachment .get ("payloadRef" ) or {}
105- attachment_id = payload .get ("id" )
143+ payload = (
144+ attachment .get ("payloadRef" )
145+ or attachment .get ("inlinePayloadRef" )
146+ or attachment .get ("payload" )
147+ or {}
148+ )
149+ attachment_id = extract_id (payload )
106150 if not attachment_id :
107151 return
108152 name = attachment .get ("filename" ) or attachment .get ("name" ) or attachment_id
@@ -141,8 +185,13 @@ def handle_attachments(
141185 seen_attachment_ids : Set [str ],
142186) -> None :
143187 for attachment in items :
144- payload = attachment .get ("payloadRef" ) or {}
145- attachment_id = payload .get ("id" )
188+ payload = (
189+ attachment .get ("payloadRef" )
190+ or attachment .get ("inlinePayloadRef" )
191+ or attachment .get ("payload" )
192+ or {}
193+ )
194+ attachment_id = extract_id (payload )
146195 if not attachment_id or attachment_id in seen_attachment_ids :
147196 continue
148197 if not is_image_attachment (attachment ):
0 commit comments