o
    l>j(                     @  sJ  d Z ddlmZ ddlZddlmZmZmZmZm	Z	 ddl
mZmZmZmZmZmZmZmZmZmZmZmZ ddlmZmZ ddlmZ ddlmZ d	d
lmZm Z m!Z! d	dl"m#Z#m$Z$m%Z%m&Z&m'Z' d	dl(m)Z)m*Z* d	dl+m,Z,m-Z-m.Z. d	dl/m0Z0m1Z1 d	dl2m3Z3m4Z4 e5e6Z7dZ8G dd de&Z9G dd de&Z:G dd deZ;dS )a  
High-level Apple Notes service built on top of a preconfigured CloudKit client.

Public API:
  - NotesService.recents(limit=50) -> Iterable[NoteSummary]
  - NotesService.recents_in_folder(folder_id, limit=20) -> Iterable[NoteSummary]
  - NotesService.iter_all(since=None) -> Iterable[NoteSummary]
  - NotesService.folders() -> Iterable[NoteFolder]
  - NotesService.in_folder(folder_id, limit=None) -> Iterable[NoteSummary]
  - NotesService.get(note_id, with_attachments=False) -> Note
  - NotesService.sync_cursor() -> str
  - NotesService.export_note(note_id, output_dir, **config_kwargs) -> str
  - NotesService.render_note(note_id, **config_kwargs) -> str
  - NotesService.iter_changes(since=None) -> Iterable[ChangeEvent]
  - NotesService.raw -> CloudKitNotesClient

Prefer the typed service methods for normal use. ``raw`` is an escape hatch for
advanced or unsupported CloudKit workflows.
    )annotationsN)DictIterableIteratorListOptional)CKErrorItem
CKFVStringCKLookupResponseCKQueryFilterByCKQueryObjectCKQueryResponseCKQuerySortByCKRecordCKReferenceCKTombstoneRecordCKZoneChangesZoneReqCloudKitExtraMode)CKReferenceFieldCKReferenceListField)BaseService)BodyDecoder   )
NOTES_ZONENOTES_ZONE_NAMENOTES_ZONE_REQ)CloudKitNotesClientNotesApiErrorNotesAuthError
NotesErrorNotesRateLimited)AttachmentIdNoteBody)
AttachmentNoteNoteSummary)NotesDesiredKeyNotesRecordType)ChangeEvent
NoteFolderHasSubfolderc                   @     e Zd ZdZdS )NoteNotFoundz#Raised when a note cannot be found.N__name__
__module____qualname____doc__ r2   r2   T/home/thesage/.local/lib/python3.10/site-packages/pyicloud/services/notes/service.pyr,   =       r,   c                   @  r+   )NoteLockedErrorz@Raised when a note is password protected and cannot be accessed.Nr-   r2   r2   r2   r3   r5   C   r4   r5   c                      s  e Zd ZdZdZdZdZdddx fddZdddyddZdddzddZ	ddd{ddZ
d|d!d"Zddd}d$d%Zd&d'd~d,d-Zdd.d/Zdd1d2Zdd3d4Zdddd6d7Zedd9d:Zdd;d<Zdd=ddAdBZeddEdFZeddIdJZddLdMZddNdOZddQdRZddTdUZddVddZd[Zedd^d_ZeddadbZddddeZ eddfdgZ!eddhdiZ"eddkdlZ#ddpdqZ$drdsddvdwZ%  Z&S )NotesServicea#  
    Typed, developer-friendly Notes API.

    The service exposes lightweight listing helpers, full-note retrieval,
    attachment download helpers, and HTML render/export utilities while hiding
    most CloudKit details behind ``NoteSummary``, ``Note``, and ``Attachment``
    models.
    zcom.apple.notes
productionprivateN)cloudkit_validation_extraservice_rootstrparamsDict[str, str]r9   CloudKitExtraMode | Nonec                  sh   t  j|||d | j d| j d| j d| j }ddd|p!i }t||||d| _i | _i | _	d S )N)r:   sessionr<   z/database/1//T)
remapEnumsgetCurrentSyncToken)base_urlr?   base_paramsvalidation_extra)
super__init__r:   
_CONTAINER_ENV_SCOPEr   _raw_folder_name_cache_attachment_meta_cache)selfr:   r?   r<   r9   endpointrD   	__class__r2   r3   rG   Z   s   "
zNotesService.__init__2   limitrT   intreturnIterable[NoteSummary]c          
   	   c  s   |dkrdS t jt jt jt jt jt jt jt jt j	g	}t
dtddtdddd	gtd
ddgd}d}d}td| 	 || }|dkrFdS | jj|t| |td||d}|jD ] }t|trz| |}	|d7 }|	V  ||krztd|  dS qZt|dd}|std dS q<)a   
        Yield the newest note summaries, ordered most-recent first.

        Args:
            limit: Maximum number of notes to yield.

        Yields:
            ``NoteSummary`` instances with lightweight metadata suitable for
            feeds, selectors, or navigation UIs.
        r   NSearchIndexesEQUALS	indexNameSTRINGrecentstypevalue
comparator	fieldName
fieldValuemodTimeF)rb   	ascending)
recordTypefilterBysortByzFetching recents: limit=%dT   queryzone_iddesired_keysresults_limitcontinuationr   z)Recents: yielded %d notes (limit reached)continuationMarkerz+Recents: no more continuation marker, done.)r&   TITLE_ENCRYPTEDSNIPPET_ENCRYPTEDMODIFICATION_DATEDELETEDFOLDERFIRST_ATTACHMENT_UTI_ENCRYPTEDFIRST_ATTACHMENT_THUMBNAIL&FIRST_ATTACHMENT_THUMBNAIL_ORIENTATIONATTACHMENTSr   r   r	   r   LOGGERdebugrK   rk   r   _coerce_keysminrecords
isinstancer   _summary_from_recordgetattr)
rN   rT   rm   rk   yieldedcont	remainingresprecsummaryr2   r2   r3   r\   v   sj   




zNotesService.recents   	folder_idc                c  s|    |dkrdS d}t d|d }td|| | j|dD ]}|j|kr;|js;|V  |d7 }||kr;td|  dS qdS )	z
        Yield recent note summaries that belong to ``folder_id``.

        This is a convenience helper that filters the global recents feed rather
        than scanning the full Notes history.
        r   Nri      z1Fetching recents in folder: folder_id=%s limit=%drS   r   z3Recents in folder: yielded %d notes (limit reached))maxrz   r{   r\   r   
is_deleted)rN   r   rT   emittedwindownr2   r2   r3   recents_in_folder   s&   	zNotesService.recents_in_folder)sincer   Optional[str]c                c  s    |  |rtd dS td|rd| nd | jjtttjg| 	t
jt
jt
jt
jt
jt
jg|dddD ]}|jD ]}t|trK| |V  q>q9dS )	ao  
        Yield note summaries from the Notes changes feed.

        Args:
            since: Optional sync token from ``sync_cursor()``. When provided, the
                iterator yields only note records changed since that cursor.

        Yields:
            ``NoteSummary`` instances for full exports, indexing jobs, or local
            cache refreshes.
        z6Skipping Notes full scan because sync token is currentNzIterating all notes%s since= FzoneIDdesiredRecordTypesdesiredKeys	syncTokenreversezone_req)_matches_current_sync_cursorrz   r{   rK   changesr   r   r'   r$   r|   r&   rq   rr   rs   rt   ru   ry   r~   r   r   r   )rN   r   zoner   r2   r2   r3   iter_all   s8   





zNotesService.iter_allIterable[NoteFolder]c           
   	   c  s    t jtg}tdtddtddddgd}d	}td
 	 | jj	|t
| |d|d}|jD ]9}t|trh|j}| |jd}t|jtpJddd	}|d	u rTd	nt|}	t|||	d	dV  | j|| q/t|dd	}|sxtd d	S q)z
        Yield top-level Notes folders as ``NoteFolder`` models.

        Use this to build folder navigation or resolve folder IDs before calling
        ``in_folder()``.
        rX   rY   rZ   r[   
parentlessr]   r`   )rf   rg   NzFetching foldersTri   rj   TitleEncryptedr2   r_   )idnamehas_subfolderscountrp   z+Folders: no more continuation marker, done.)r&   rq   _HAS_SUBFOLDER_FIELDr   r   r	   rz   r{   rK   rk   r   r|   r~   r   r   
recordName_decode_encryptedfields	get_valuer   	get_fieldboolr)   rL   
setdefault)
rN   rm   rk   r   r   r   r   r   has_sub_valuehas_subr2   r2   r3   folders   sZ   






zNotesService.foldersOptional[int]c                c  s    d}t d|| | jjtttjg| t	j
t	jt	jt	jt	jt	jt	jt	jt	jg	dddD ]>}|jD ]8}t|ts;q3| |}t|jdpHd}|sP||krQq3| |V  |d7 }|rk||krkt d	|   d
S q3q.d
S )z
        Yield non-deleted notes in ``folder_id``, ordered newest first.

        Args:
            folder_id: Folder identifier from ``folders()`` or note metadata.
            limit: Optional maximum number of notes to yield.
        r   z/Fetching notes in folder: folder_id=%s limit=%sT)r   r   r   r   r   DeletedFr   z1Notes in folder: yielded %d notes (limit reached)N)rz   r{   rK   r   r   r   r'   r$   r|   r&   rq   rr   rs   rt   ru   rv   rw   rx   ry   r~   r   r   _extract_folder_idr   r   r   r   )rN   r   rT   r   r   r   rec_folder_iddeletedr2   r2   r3   	in_folder/  sR   




zNotesService.in_folderF)with_attachmentsnote_idr   r   r$   c          
      C  s>  t d|| | j|tjtjtjtjtjtj	dgd}| 
|}|jr4t d| td|jp/| d| |}z|rK|jrKt d|jt|j nt d|j W n tyi   t d	|rd|jrdd
nd Y nw d}d}|rs|jnd}g }	|r|jr|j}	|r| j||	d}t|j|j|j|j|j|j|j|j|||dS )a  
        Return a single note with decoded text and optional attachment metadata.

        Args:
            note_id: The CloudKit note record identifier.
            with_attachments: When ``True``, resolve ``Attachment`` metadata and
                include it on the returned ``Note``.

        Raises:
            NoteNotFound: If the note does not exist.
            NoteLockedError: If the note is passphrase-locked and its content
                cannot be read.
        z-Fetching note: note_id=%s with_attachments=%sTextDataEncryptedrm   z%Note is locked and cannot be read: %szNote 'z' is locked and cannot be read.z"notes.body.decoded ok id=%s len=%dznotes.body.decoded empty id=%sznotes.body.decoded %sokemptyNattachment_ids)r   titlesnippetmodified_atr   folder_namer   	is_lockedtexthtmlattachments)rz   r{   _lookup_note_recordr&   rq   rr   rs   rt   ru   ry   r   r   warningr5   r   _decode_note_bodyr   infor   len	Exceptionr   _resolve_attachments_for_recordr$   r   r   r   r   r   )
rN   r   r   targetr   	note_bodyr   r   r   r   r2   r2   r3   get`  sv   



zNotesService.getc                 C  s   t d | jjtdS )z
        Return the current Notes sync token.

        Persist this token and pass it back to ``iter_all(since=...)`` or
        ``iter_changes(since=...)`` on a later run to perform incremental syncs.
        z#Fetching sync cursor for Notes zone	zone_name)rz   r{   rK   current_sync_tokenr   rN   r2   r2   r3   sync_cursor  s   
zNotesService.sync_cursor
output_dirc           
      K  sb   |  |}ddlm} ddlm} |di |}|| j|d}|j||d}	|	s/td| |	S )	a  
        Export a note to HTML on disk and return the generated file path.

        Args:
            note_id: The UUID of the note to export.
            output_dir: Directory where the HTML file will be written.
            **config_kwargs: Keyword arguments forwarded to ``ExportConfig``,
                including ``export_mode``, ``assets_dir``, ``full_page``,
                ``preview_appearance``, ``pdf_object_height``, and link behavior
                settings.

        Returns:
            The path to the generated HTML file.

        Notes:
            By default, this produces archival output: a full HTML page with
            local asset downloads.
        r   )NoteExporterExportConfig)config)r   zFailed to export note: Nr2   )r   rendering.exporterr   rendering.optionsr   rK   exportr   )
rN   r   r   config_kwargsr   r   r   r   exporterpathr2   r2   r3   export_note  s   
zNotesService.export_notec                 K  sx   |  |}ddlm}m} ddlm} ddlm} |di |}||}	|	s(dS || j||	|\}
}||}|	|	|
S )a  
        Render a note to an HTML fragment string.

        Args:
            note_id: The UUID of the note to render.
            **config_kwargs: Keyword arguments forwarded to ``ExportConfig`` to
                tune preview selection or link behavior for the rendered HTML.

        Returns:
            An HTML fragment string. This method does not download assets or
            write files to disk.
        r   )build_datasourcedecode_and_parse_noter   )NoteRendererr   Nr2   )
r   r   r   r   r   r   rendering.rendererr   rK   render)rN   r   r   r   r   r   r   r   r   noteds_rendererr2   r2   r3   render_note  s   
zNotesService.render_noteIterable[ChangeEvent]c                c  sr   |  |rtd dS td|rd| nd | jjtttjg| 	t
jt
jt
jt
jt
jt
jg|dddD ]}}|jD ]w}t|tr`t|jd	pMd}|rSd
nd}t|| |dV  q>t|trt|dd}|r~td
t|dddddddddV  q>t|tr|j|j|jd}t d|jpd|j|j t!d|jpd |dt d| t!ddt"|idq9dS )z
        Yield ``ChangeEvent`` items from the Notes changes feed.

        Pass a sync token from ``sync_cursor()`` to process only new changes
        since a previous run.
        z8Skipping Notes change scan because sync token is currentNzIterating changes%sr   r   Fr   r   r   r   updated)r^   r   r   Tr   r   r   r   r   r   r   r   )serverErrorCodereasonr   z;CloudKit error during change enumeration: %s (%s) record=%sUNKNOWNz*CloudKit error during change enumeration: )payloadz*Unexpected record type in changes feed: %rz&Unexpected record type in changes feedrecord_repr)#r   rz   r{   rK   r   r   r   r'   r$   r|   r&   rq   rr   rs   rt   ru   ry   r~   r   r   r   r   r   r(   r   r   r   r%   r   r   r   r   errorr   repr)rN   r   r   r   deleted_flagevt_typerecord_namedetailsr2   r2   r3   iter_changes  s   








zNotesService.iter_changesr   c                 C  s   | j S )z
        Return the authenticated low-level Notes CloudKit client.

        This is an escape hatch for advanced or unsupported operations; prefer
        the typed service methods above for normal use.
        )rK   r   r2   r2   r3   rawJ  s   zNotesService.rawc              
   C  sP   |sdS z
| j jtd|kW S  ty' } ztd| W Y d}~dS d}~ww )z>Return whether an incremental Notes cursor is already current.Fr   z(Failed to preflight Notes sync token: %sN)rK   r   r   r   rz   r   )rN   r   excr2   r2   r3   r   V  s   z)NotesService._matches_current_sync_cursorr   rm   Optional[Iterable[object]]r   c                C  sZ   | j j|g| |d}|jD ]}t|tr|j|kr|  S qtd| t	d| )z2Return one Notes record or raise ``NoteNotFound``.)record_namesrm   zNote not found: %szNote not found: )
rK   lookupr|   r~   r   r   r   rz   r   r,   )rN   r   rm   r   r   r2   r2   r3   r   a  s   
z NotesService._lookup_note_recordkeysOptional[List[str]]c                 C  sD   | d u rd S g }| D ]}t |tr||j q
|t| q
|S N)r   r&   appendr_   r;   )r  outkr2   r2   r3   r|   s  s   
zNotesService._coerce_keysbOptional[bytes | str]c                 C  s>   | d u rd S t | tr| S z| ddW S  ty   Y d S w Nzutf-8replace)r   r;   decoder   )r  r2   r2   r3   r     s   
zNotesService._decode_encryptedr   c                 C  sP   |j d}t|tr|jr|jjS |j d}t|tr&|jr&|jd jS d S )NFolderFoldersr   )r   r   r   r   r_   r   r   )rN   r   fflr2   r2   r3   r     s   zNotesService._extract_folder_idc              
   C  s   |sd S || j v r| j | S z6| jj|gdgd}d }|jD ]}t|tr5|j|kr5| |j	d} nq|| j |< t
d|| |W S  tttfy\   d | j |< t
d| Y d S w )Nr   r   z*Folder name resolved: folder_id=%s name=%sz+Failed to resolve folder name: folder_id=%s)rL   rK   r  r~   r   r   r   r   r   r   rz   r{   r   r   r    r   )rN   r   r   r   r   r2   r2   r3   _folder_name  s,   





zNotesService._folder_namer%   c           	   
   C  s   |  |jd}|  |jd}|jd}t|jdp d}| |}| |}tt|dd t	j
 k}t|j|||||||dS )	Nr   SnippetEncryptedModificationDater   Frf   r   r   )r   r   r   r   r   r  r;   r   lowerr'   PasswordProtectedNoter%   r   )	rN   r   r   r   modifiedr   r   r   r   r2   r2   r3   r     s,   

z!NotesService._summary_from_recordOptional[NoteBody]c              
   C  s   |j d}|std|j dS z+t |}|r"t|tr"|W S td|j|r7t	|ddr7t
t	|ddnd W dS  tyW } ztd|j| W Y d}~dS d}~ww )	zADecode TextDataEncrypted into a NoteBody (text + attachment IDs).r   z*notes.body.missing TextDataEncrypted id=%sNz!notes.body.no_text id=%s bytes=%sbytes    0z%notes.body.decode_failed id=%s err=%s)r   r   rz   r{   r   r   r  r   r"   r   r   r   r   )rN   r   r   nber2   r2   r3   r     s.   
	zNotesService._decode_note_bodyr   r   Optional[List[AttachmentId]]List[Attachment]c             
     sl  g }g }g }|r|D ]}t |dd}|r|| q
|jd}|rFt|drFt |dg p.g }	|	D ]}
t |
dd}|rE|| || q1t }g }|D ]}||vr]|| || qMtd|| |sk|sk|S t }g }|rt|n|}|D ]}||vr|| || qx fdd|D }|rg d	}z
 j	j
||d
}W n ty } ztd|| tg d}W Y d}~nd}~ww t |dg D ]B}t|tsqȈ |}|stdt |ddt |ddrt|j nd q|j} ||}|D ]}| j|< td|| qq|p|D ]} j|}|r%||vr%|| qtdt|t |dd |S )zHydrate attachment metadata for a note.

        Combines attachment identifiers from CloudKit references and the decoded
        protobuf body. Missing records are skipped gracefully.
        
identifierNAttachmentsr_   r   z/notes.attachments.candidates alias=%s lookup=%sc                   s   g | ]	}| j vr|qS r2   )rM   ).0aidr   r2   r3   
<listcomp>  s    z@NotesService._resolve_attachments_for_record.<locals>.<listcomp>)FilenameAttachmentUTIUTISize	ThumbnailFirstAttachmentThumbnailPrimaryAssetPreviewImagesPreviewAppearancesFallbackImageFallbackPDFPaperAssetsAttachmentIdentifierattachmentIdentifier
Identifierr   z-notes.attachments.lookup_failed ids=%s err=%s)r~   r~   z/notes.attachments.unhandled record=%s fields=%sr   z)notes.attachments.cached base=%s alias=%sz#Resolved %d attachments for note %s)r   r  r   r   hasattrsetaddrz   r{   rK   r  r   r
   r   r   _build_attachment_from_recordlistr  r   _attachment_aliasesrM   r   r   )rN   r   r   r  	alias_idslookup_candidatesr"  identfldrefsrefrn
seen_aliasidscidseen_lookup
lookup_idslookup_source_idsmissingrm   r   errrec_a
attachmentbase_idaliasesaliasattr2   r   r3   r     s   













z,NotesService._resolve_attachments_for_recordnames	List[str]c              	   C  sf   |D ].}| j |}|d u rqt|tr'z	|ddW   S  ty&   Y qw t|tr0|  S qd S r
  )r   r   r   r  r  r   r;   r   rN  r   vr2   r2   r3   _coerce_stringV  s   

zNotesService._coerce_stringr   c                 C  s0   |g}t | g d}|r||vr|| |S )N)r0  r1  r2  )r6   rR  r  )r   r   rK  r  r2   r2   r3   r8  e  s   
z NotesService._attachment_aliasesOptional[Attachment]c           	   	   C  s   t |dd }|s
d S | |g d}| |ddg}| |ddg}d }d }d }|p0| |dg}|p9| |dg}|pA| |d}|pI| |d	}|pQ| |d
}|pZ| |dg}|pd| |ddg}|ph|}t|||||||dS )Nr   )r$  NameFileNamer%  r&  r'  FileSizer*  r.  r/  r+  r-  r(  r)  )r   filenameutisizedownload_urlpreview_urlthumbnail_url)r   rR  _coerce_int_coerce_asset_url_coerce_asset_url_from_listr#   )	rN   r   r"  rW  rX  rY  rZ  r[  r\  r2   r2   r3   r6  o  sD   z*NotesService._build_attachment_from_recordc                 C  s`   |D ]+}| j |}t|tr|  S t|trt|  S t|tr-| r-t|  S qd S r  )r   r   r   rU   floatr;   isdigitrP  r2   r2   r3   r]    s   

zNotesService._coerce_intc                 C  sJ   |D ] }| j |}|sqt|dd }t|dd }t|tr"|  S qd S Nr_   downloadURL)r   r   r   r   r;   )r   rN  r   r<  valurlr2   r2   r3   r^    s   
zNotesService._coerce_asset_urlr   c                 C  sx   | j |}|s
d S t|dd }t|ttfr:|D ] }d }t|tr(|d}nt|dd }t|tr9|r9|  S qd S rb  )	r   r   r   r   r7  tupledictr   r;   )r   r   r<  rd  tokenre  r2   r2   r3   r_    s   
z(NotesService._coerce_asset_url_from_listrM  r#   	directoryc                 C  s<   |j p|jp|j}|stdtd|j| | j||S )N*Attachment does not expose a download URL.zDownloading attachment %s to %s)	rZ  r[  r\  r   rz   r{   r   rK   download_asset_to)rN   rM  ri  re  r2   r2   r3   _download_attachment_to  s
   z$NotesService._download_attachment_toi   
chunk_sizern  Iterator[bytes]c                c  sJ    |j p	|jp	|j}|stdtd|j| | jj||dE d H  d S )Nrj  z%Streaming attachment %s chunk_size=%drm  )	rZ  r[  r\  r   rz   r{   r   rK   download_asset_stream)rN   rM  rn  re  r2   r2   r3   _stream_attachment  s   zNotesService._stream_attachment)r:   r;   r<   r=   r9   r>   )rT   rU   rV   rW   )r   r;   rT   rU   rV   rW   )r   r   rV   rW   )rV   r   )r   r;   rT   r   rV   rW   )r   r;   r   r   rV   r$   )rV   r;   )r   r;   r   r;   rV   r;   )r   r;   rV   r;   )r   r   rV   r   )rV   r   )r   r   rV   r   )r   r;   rm   r   rV   r   )r  r   rV   r  )r  r	  rV   r   )r   r   rV   r   )r   r   rV   r   )r   r   rV   r%   )r   r   rV   r  )r   r   r   r  rV   r  )r   r   rN  rO  rV   r   )r   r   r   r;   rV   rO  )r   r   rV   rS  )r   r   rN  rO  rV   r   )r   r   r   r;   rV   r   )rM  r#   ri  r;   rV   r;   )rM  r#   rn  rU   rV   ro  )'r.   r/   r0   r1   rH   rI   rJ   rG   r\   r   r   r   r   r   r   r   r   r   propertyr   r   r   staticmethodr|   r   r   r  r   r   r   rR  r8  r6  r]  r^  r_  rl  rq  __classcell__r2   r2   rP   r3   r6   L   s`    	B
(61
P


 T



	

|
	*
r6   )<r1   
__future__r   loggingtypingr   r   r   r   r   pyicloud.common.cloudkitr   r	   r
   r   r   r   r   r   r   r   r   r   pyicloud.common.cloudkit.modelsr   r   pyicloud.services.baser    pyicloud.services.notes.decodingr   
_constantsr   r   r   clientr   r   r   r   r    domainr!   r"   modelsr#   r$   r%   models.constantsr&   r'   
models.dtor(   r)   	getLoggerr.   rz   r   r,   r5   r6   r2   r2   r2   r3   <module>   s&    8
	