
    =[j                        U d Z ddlZddlZddlZddlZddlZddlmZ ddlm	Z	m
Z
mZmZmZ ddlmZ ddlmZmZmZmZmZmZ ddlmZ ddlZddlmZmZ dd	lmZ dd
lm Z m!Z!m"Z"m#Z#m$Z$ ddl%m&Z& ddl'm(Z( ddl)m*Z* ddl+m,Z,m-Z-m.Z. ddl/m0Z0m1Z1m2Z2m3Z3m4Z4m5Z5m6Z6m7Z7m8Z8m9Z9 ddl:m;Z;m<Z<m=Z=m>Z> ddl?m@Z@mAZAmBZBmCZCmDZDmEZEmFZFmGZGmHZHmIZI ddlJmKZK ddlLmMZM ddlNmOZO ddlPmQZQmRZR ddlSmTZTmUZUmVZV  ejW        eX          ZYejZ        e[d<   dZ\e]e[d<   dZ^e]e[d<   ddiZ_e`eaeaf         e[d<   e- de-d d!d"d#d$d%d&d  ejb        e_d         d'd(d)d&d*d+,          d-Zce`eaeaf         e[d.<   d/d0d1Zde`eaeaf         e[d2<   d>d3eea         d4eafd5Ze ed67           G d8 d9                      Zf ed67           G d: d;                      Zg G d< d=          ZhdS )?zLibrary base file.    N)	dataclass)chmodenvironmakedirspathumask)
gettempdir)AnyDictListMappingOptionalcast)uuid1)DefaultClientDataCollectorFido2Client)CtapHidDevice)AuthenticationResponsePublicKeyCredentialDescriptor!PublicKeyCredentialRequestOptionsPublicKeyCredentialTypeUserVerificationRequirement)	HTTPError)Response)CloudKitExtraMode)ACCOUNT_NAMECONTENT_TYPE_JSONCONTENT_TYPE_TEXT)
PyiCloud2FARequiredExceptionPyiCloudAcceptTermsExceptionPyiCloudAPIResponseExceptionPyiCloudFailedLoginException PyiCloudNoTrustedNumberAvailablePyiCloudPasswordException$PyiCloudServiceNotActivatedExceptionPyiCloudServiceUnavailable$PyiCloudTrustedDevicePromptException*PyiCloudTrustedDeviceVerificationException)Hsa2BootContextTrustedDeviceBridgeBootstrapperTrustedDeviceBridgeStateparse_boot_args_html)
AccountServiceAppleDeviceCalendarServiceContactsServiceDriveServiceFindMyiPhoneServiceManagerHideMyEmailServicePhotosServiceRemindersServiceUbiquityService)InvitesService)NotesService)PyiCloudSession)SrpPasswordSrpProtocolType)
b64_encodeb64url_decodeget_password_from_keyringLOGGER   PCS_SLEEP_TIME
   PCS_MAX_RETRIES
User-AgentzwMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3.1 Safari/605.1.15_HEADERSz, text/javascript@d39ba9916b7251055b22c7f910e2ea796ee65e98b2ddecea8f5dde8d9d1a815dfirstPartyAuthhttps://www.icloud.comtrueweb_messagecode zen-USz	GMT+00:00z1.1)ULZVF),:)
separators)AcceptContent-TypezX-Apple-OAuth-Client-IdzX-Apple-OAuth-Client-TypeX-Apple-OAuth-Redirect-URIz X-Apple-OAuth-Require-Grant-CodeX-Apple-OAuth-Response-ModeX-Apple-OAuth-Response-TypeX-Apple-OAuth-StateX-Apple-Widget-KeyzX-Apple-FD-Client-Info_AUTH_HEADERS_JSON2534Project662534B22)clientBuildNumberclientMasteringNumber_PARAMScookie_directoryreturnc                     | r&t          j        t          j        |                     S t          j        t	                      d          }t          j        |t          j                              S )zAResolve the directory used for persisted session and cookie data.pyicloud)r   normpath
expanduserjoinr	   getpassgetuser)rb   topdirs     U/home/thesage/.hermes/hermes-agent/venv/lib/python3.11/site-packages/pyicloud/base.pyresolve_cookie_directoryrm   m   sW      @}T_-=>>???)JLL*55F9VW_..///    T)frozenc                       e Zd ZU dZeez  ed<   dZee	         ed<   dZ
ee         ed<   edeeeef                  ded          fd            Zdeeef         fd	ZdS )
TrustedPhoneNumberz-Typed view of Apple's trusted-phone metadata.	device_idNnon_fteu	push_modevaluerc   c                 V   t          |t                    sdS |                    d          }t          |t          t          f          sdS |                    d          }t          |t
                    sd}|                    d          }|t	          |          } | |||          S )z>Return a typed phone record when Apple's payload includes one.NidnonFTEUpushMode)rr   rs   rt   )
isinstancer   getintstrbool)clsru   rr   rs   rt   s        rl   from_mappingzTrustedPhoneNumber.from_mapping   s     %)) 	4IIdOO	)c3Z00 	499Y''(D)) 	HIIj))	 IIs
 
 
 	
rn   c                 :    d| j         i}| j        
| j        |d<   |S )zHReturn the nested phoneNumber payload expected by Apple's SMS endpoints.rw   Nrx   )rr   rs   )selfpayloads     rl   as_phone_number_payloadz*TrustedPhoneNumber.as_phone_number_payload   s)     $("8=$!%GIrn   )__name__
__module____qualname____doc__r|   r}   __annotations__rs   r   r~   rt   classmethodr   r
   r   dictr    rn   rl   rq   rq   w   s         77Sy#Hhtn####Ix}###
WS#X./
	&	'
 
 
 [
2c3h      rn   rq   c                       e Zd ZU dZdZee         ed<   dZe	edf         ed<   e
deeeef                  dd fd	            Zdee         fd
ZdS )PhoneNumberVerificationz9Typed view of Apple's phone verification wrapper payload.Ntrusted_phone_numberr   .trusted_phone_numbersru   rc   c                    t          |t                    s
 |             S t                              |                    d                    }|                    d          }g }t          |t
                    r6|D ]3}t                              |          }||                    |           4 | |t          |                    S )zDReturn the parsed phone verification payload when Apple exposes one.trustedPhoneNumbertrustedPhoneNumbersNr   r   )rz   r   rq   r   r{   listappendtuple)r   ru   r   trusted_phone_numbers_rawr   entryphone_numbers          rl   r   z$PhoneNumberVerification.from_mapping   s     %)) 	355L1>>II*++ 
  
 %*II.C$D$D!:</66 	?2 ? ?1>>uEE+)00>>>s!5"'(=">">
 
 
 	
rn   c                 J    | j         | j         S | j        r| j        d         S dS )zBReturn the first usable trusted phone number from Apple's payload.Nr   r   r   s    rl   best_trusted_phone_numberz1PhoneNumberVerification.best_trusted_phone_number   s4     $0,,% 	1-a00trn   )r   r   r   r   r   r   rq   r   r   r   r   r   r}   r
   r   r   r   rn   rl   r   r      s         CC9=(#56===<>5!3S!89>>>
WS#X./
	"
 
 
 [
284F+G      rn   r   c                      e Zd ZdZdcdZdddee         defdZ	 	 	 	 	 	 	 	 deddd	d
edee         dee         dedee         dedee         dede	dz  dedee
         ddfdZedefd            Z	 dfdedee         ddfdZdeddfdZdcdZdcdZdcdZdeeef         fdZddddd ed!ed"edeeef         fd#Zdcd$Zdcd%Zdcd&Zdee         ddfd'Zdefd(Z	 ddd)eeeef                  deeef         fd*Zedefd+            Zdefd,Zedefd-            Z edefd.            Z!edefd/            Z"ede#eeef                  fd0            Z$d1eeef         defd2Z%d1eeef         d3edefd4Z&de'fd5Z(dcd6Z)	 ddd7ed8ee         ddfd9Z*de+fd:Z,defd;Z-defd<Z.ddd8ee         defd=Z/edefd>            Z0edee         fd?            Z1edee2e                  fd@            Z3dAe'ddfdBZ4de5fdCZ6dee7         fdDZ8dee         fdEZ9defdFZ:ede2e;         fdG            Z<ddd1ee;         ddfdHZ=deeef         fdIZ>dJedKedeeef         fdLZ?dJeddfdMZ@d3edefdNZAd3eddfdOZBd3eddfdPZCdefdQZDdRedefdSZEedeFfdT            ZGedeHfdU            ZIedeJfdV            ZKedeLfdW            ZMedeNfdX            ZOedePfdY            ZQedeRfdZ            ZSedeTfd[            ZUedeVfd\            ZWedeXfd]            ZYedeZfd^            Z[ede\fd_            Z]edefd`            Z^defdaZ_defdbZ`dS )gPyiCloudServicea!  
    A base authentication class for the iCloud service. Handles the
    authentication required to access iCloud services.

    Usage:
        from pyicloud import PyiCloudService
        pyicloud = PyiCloudService('username@apple.com', 'password')
        pyicloud.iphone.location()
    rc   Nc                 x    | j         rdnd}d| | _        | j         d| _        d| | _        d| d| _        dS )	z%Set up the endpoints for the service.z.cnrL   zhttps://idmsa.apple.comz/appleauth/authrH   zhttps://setup.icloud.comz/setup/ws/1N)_is_china_mainland_idmsa_endpoint_auth_endpoint_home_endpoint_setup_endpoint)r   icloud_chinas     rl   _setup_endpointsz PyiCloudService._setup_endpoints   sb     &*%<DEE"$Ll$L$L&*&:#K#K#K#JL#J#J$X|$X$X$Xrn   rb   c                    t          |          }|s5t          j        |          }t          |d           t	          |d           t          d          }	 t          |d           t          |           n# t          |           w xY w|S )z,Set up the cookie directory for the service.T)exist_oki  ?   )rm   r   dirnamer   r   r   )r   rb   _cookie_directoryrk   	old_umasks        rl   _setup_cookie_directoryz'PyiCloudService._setup_cookie_directory   s    45EFF 	"\"344FVd++++&&!!!%LL		&6666)E)  s   A8 8B	TF)authenticatecloudkit_validation_extraapple_idpasswordverify	client_idwith_familychina_mainlandaccept_termsrefresh_intervalr   r   c
                   |t          j        dd          dk    n|| _        |                                  || _        || _        || _        |	| _        | j        |
rt          |          | _        i | _	        i | _
        d| _        d| _        t                      | _        d| _        d| _        i | _        |p,t%          t'                                                                | _        || _        || _        |                     |          }t2                                          }|                    | j        | j         dd           t;          | ||| j        |          | _        | j        j	                            d	| j                  | _        t@                                          }|                    d
| j        i           || _        d| _!        d| _"        d| _#        d| _$        d| _%        d| _&        d| _'        d| _(        d| _)        d| _*        d| _+        d| _,        d| _-        |
r| .                                 dS dS )z6Initialize a service session for one Apple ID account.Nr   01unknown/)OriginReferer)r   headersr   rb   r   clientIdF)/r   r{   r   r   _password_raw	_apple_id_accept_terms_refresh_intervalr>   data
_auth_data_hsa2_boot_context_trusted_device_bridge_stater*   _trusted_device_bridge_two_factor_delivery_method_two_factor_delivery_noticeparamsr}   r   lower
_client_id_with_family_cloudkit_validation_extrar   rE   copyupdater   r9   _sessionsessionra   _webservices_account	_calendar	_contacts_devices_drive_files_hidemyemail_photos
_reminders_notes_invites_requires_mfar   )r   r   r   rb   r   r   r   r   r   r   r   r   r   _headers_paramss                  rl   __init__zPyiCloudService.__init__   sT   $ % K,,33 	
 	,4&#//?%,%!:8!D!DD$&	*,=APT)&E&G&G#09(:>(&((@CLL,>,>,@,@"-*C'!%!=!=>N!O!O#+==??-"1444 	
 	
 	
 *9o.*
 *
 *
 ,+//T_MM"),,..DO	
 	
 	

 AE264848>B.215:>046:.226#( 	 	  	 rn   c                     | j         S )zAReturn whether the current service uses China mainland endpoints.)r   r   s    rl   is_china_mainlandz!PyiCloudService.is_china_mainlandL  s     &&rn   force_refreshservicec                 (   d}| j         j                            d          rI|sG	 |                                 | _        d}n*# t          $ r t
                              d           Y nw xY w|s|| j                            d          r|| j        d         v r| j        d         |         }d|v rl|d         rdt
                              d| j        |           	 |                     |           d}n*# t          $ r t
                              d	           Y nw xY w|sa	 | 
                                 t
                              d
           n1# t          $ r$ d| _        t
                              d           Y nw xY w|                                  dS )z
        Handles authentication, and persists cookies so that
        subsequent logins will not cause additional e-mails from Apple.
        Fsession_tokenTz7Invalid authentication token, will log in from scratch.NappscanLaunchWithOneFactorzAuthenticating as %s for %sz7Could not log into service. Attempting brand new login.z%Authentication completed successfullyz2FA is required)r   r   r{   _validate_tokenr!   r?   debugaccount_name&_authenticate_with_credentials_servicer"   _authenticater   r   _update_state)r   r   r   login_successfulapps        rl   r   zPyiCloudService.authenticateR  s    !<  11 	X- 	XX 0022	#'  / X X XVWWWWWX !	#	f%% $49V,,,"&)F"3G"<C'3..37O3P.:D<MwWWW??HHH'+$$3   LLQ    
   	00""$$$DEEEE/ 0 0 0%)"./////0 	s5   A $A('A(C1 1$DD.E +E;:E;
login_datac           	         | j                             d          rd| j        st          d          | j                            | j         d| j        d| j                             di                               dd          i          }|                                 |                                }|                    d	i                               d
          }|t          d          | j                            | j         d| j        d|i          }|                                 | j        	                    | j         d|          }|                                 |                                | _         dS dS )z*Handle accepting updated terms of service.termsUpdateNeededz\You must accept the updated terms of service to continue. Set --accept-terms to accept them.z	/getTermslocaledsInfolanguageCodeen_USr   jsoniCloudTermsversionNzCould not get terms versionz/repairDoneacceptedICloudTerms/accountLoginr   )
r   r{   r   r    r   r   r   raise_for_statusr   post)r   r   resp
terms_infor   s        rl   _handle_accept_termsz$PyiCloudService._handle_accept_terms}  s   9==,-- 	$% 29   "\--'222{dimmHb99==ngVV .  D !!###)-J",.."C"C"G"G	"R"RG23PQQQ<##'444{+W5 $  D
 !!###<$$'666Z %  D !!###		DIII=	$ 	$rn   c                 
   d| j         v r\t          | j         d         t                    r<d| j         d         v r-| j                            d| j         d         d         i           d| j         v r| j         d         | _        dS dS )z Update the state of the service.r   dsidwebservicesN)r   rz   r   r   r   r   r   s    rl   r   zPyiCloudService._update_state  s     	!!49X.55 "$)H---K	((;F(CDEEEDI%% $	- 8D &%rn   c                 R   i | _         i | _        d| _        |                                  d| _        d| _        d| _        d| _        d| _        d| _	        d| _
        d| _        d| _        d| _        d| _        d| _        d| _        d| _        | j                            dd           dS )z#Clear in-memory auth-derived state.Nr   Fr
  )r   r   r   "_clear_trusted_device_bridge_stater   r   r   r   r   r   r   r   r   r   r   r   r   r   r   popr   s    rl   _clear_authenticated_statez*PyiCloudService._clear_authenticated_state  s     	"&//111+4(+/(  "%%%%%rn   c                 `    | j         | j                            | j                    d| _         dS )zIClose any active trusted-device bridge session and clear in-memory state.N)r   r   closer   s    rl   r  z2PyiCloudService._clear_trusted_device_bridge_state  s5     ,8'--d.OPPP,0)))rn   c                    ddddd}| j         j                            d          s|                                  |S | j         j                            d          s|                                  |S 	 |                                 | _        |                                  n&# t          $ r |                                  |cY S w xY w|                    d| j	        | j
        | j        d           |S )z?Probe current authentication state without prompting for login.F)authenticatedtrusted_sessionrequires_2farequires_2sar   X-APPLE-WEBAUTH-TOKENT)r   r   r{   r  cookiesr   r   r!   r   is_trusted_sessionr  r  )r   statuss     rl   get_auth_statuszPyiCloudService.get_auth_status  s$    #$!!	"
 "
 | $$_55 	++---M|#''(?@@ 	++---M	,,..DI    + 	 	 	++---MMM	 	!%#'#: $ 1 $ 1	 	
 	
 	
 s   3-B! ! CC)keep_trustedall_sessionsclear_local_sessionr  r  r  c                b   ||d}d}| j                             d          r| j        j                            d          r	 | j                            | j         dt          | j                   t          j        |          ddi          }t          |                                                    d	                    }n3# t          t          f$ r t                              d
d           Y nw xY wd}|r1| j                            d           |                                  d}|||dS )zFLog out of the current session and optionally clear local persistence.)trustBrowserallBrowsersFr
  r  z/logoutrV   ztext/plain;charset=UTF-8)r   r   r   successz Remote logout was not confirmed.Texc_info)remove_files)r   remote_logout_confirmedlocal_session_cleared)r   r{   r   r  r  r   r   r   dumpsr~   r!   
ValueErrorr?   r   clear_persistencer  )r   r  r  r  r   r&  responser'  s           rl   logoutzPyiCloudService.logout  sn    )'$
 $
 #(;??6"" 	Pt|';'?'?#(
 (
 	P	P<,,+444,,G,,+-GH	 -   +/x}}/B/B9/M/M*N*N''0*= P P P?$OOOOOP !& 	)L***===++---$(! '>%:
 
 	
s   B C -C32C3c                     t                               d| j                   	 |                                  dS # t          t
          f$ r, |                                  |                                  Y dS w xY w)zGAuthenticate with either the cached session token or fresh credentials.zAuthenticating as %sN)r?   r   r   _authenticate_with_tokenr"   r   _srp_authenticationr   s    rl   r   zPyiCloudService._authenticate  s    +T->???	,))+++++,.JK 	, 	, 	,$$&&&))++++++	,s   8 9A54A5c                    | j         t          d          |                                 }| j                            | j         d|d         d|d         |d         |d         |d         |d	         |d         d
d	          }|                                 t          | j                   }t          j	                     t          j
                     	 t          j        | j        |t          j        t          j                  }|                                \  }}dt!          |          t"          |dd t$          D             i}| j                            | j         d||                                           }|                                 n2# t(          t*          t,          f$ r}d}	t          |	|          |d}~ww xY w|                                }
t1          j        |
d                   }t1          j        |
d                   }|
d         }|
d         }t%          |
d                   }d}|                    ||||           |                    ||          }|j        }|r/|r-t"          |d|dt!          |          dt!          |          dddg i}| j        j                            d          r#| j        j                            d          g|d<   	 | j                            | j         d d!d"i||                                 #           dS # t<          $ rK t>                               d$           | !                                | _"        | #                                 Y dS t(          $ r}d%}	t          |	          |d}~ww xY w)&zSRP authentication.NzNo password setz/authorize/signinrZ   7r[   rY   rW   rX   latest)	frame_id	skVersioniframeidr   response_typeredirect_uriresponse_modestateauthVersionr   )hash_algng_typea	protocolsc                     g | ]	}|j         
S r   )ru   ).0protocols     rl   
<listcomp>z7PyiCloudService._srp_authentication.<locals>.<listcomp>D  s    MMMhnMMMrn   z/signin/initr   r   z&Failed to initiate srp authentication.saltbc	iterationrB      m1m2
rememberMeTtrustTokenstrust_tokenz/signin/completeisRememberMeEnabledrI   )r   r   r   z(2FA required to complete authentication.#Invalid email/password combination.)$r   r"   _get_auth_headersr   r{   r   r  r:   srprfc5054_enableno_username_in_xUserr   SHA256NG_2048start_authenticationr<   r   r;   r  r!   r   r$   r   base64	b64decodeset_encrypt_infoprocess_challengeH_AMKr   r   r?   r   _get_mfa_auth_optionsr   _request_2fa_code)r   auth_headersr+  srp_passwordusrunameAr   errormsgbodyrE  rF  rG  
iterationsrB  
key_lengthrJ  rK  s                      rl   r/  z#PyiCloudService._srp_authentication  s   %./@AAA--//!\--"555()>? ()>?)*>?!-.K!L ,-I J!-.K!L%&;<'
 
 . 
 
 	!!###$/0B$C$C	F(!	  C //11HE1Z]]eMM_MMM$D "&!2!2&444..00 "3 " "H
 %%''''(%
 	F 	F 	F
 ;C.sE::E	F  (}}&tF|44#DI..c{+
$3D4D$E$E
%%dJ
HMMM..tQ779 	" 	eQjnnjnndrD <  // 	I#'<#4#8#8#G#G"HD	?L&888)6 ..00       , 	% 	% 	%LLCDDD"88::DO""$$$$$$+ 	? 	? 	?7C.s33>	?s8   B<F F>&F99F>:L AN	)	N	2NN	c                    | j         j                            d          st          d          	 | j         j                            d          | j         j                            d          d| j         j                            dd          d}| j                             | j         d|	          }|                                 |                                | _        |                     |           | j	        st          | j        |          i | _        d
| _        |                                  |                     d           d
S # t           t"          f$ r}d}t          ||          |d
}~ww xY w)z!Authenticate using session token.r   zNo session token availableaccount_countryTrN  rL   )accountCountryCodedsWebAuthTokenextended_login
trustTokenr  r  Nr   zInvalid authentication token.)r   r   r{   r"   r  r   r  r   r  r  r   r   r   r   r  _set_two_factor_delivery_stater!   r   )r   r   r  re  rf  s        rl   r.  z(PyiCloudService._authenticate_with_token~  s   | $$_55 	M./KLLL	F&*l&7&;&;<M&N&N"&,"3"7"7"H"H"&"l/33M2FF	* *J "\..'666Z /  D !!###		DI%%j111* L243DdKKK DO&*D#33555//	:::::,i8 	F 	F 	F1C.sE::E	Fs   DE
 
E3E..E3c                    || j         | j        d}	 | j                            | j         d|           |                     |           |                                 | _        dS # t          $ r}d}t          ||          |d}~ww xY w)z5Authenticate to a specific service using credentials.)appNamer   r   r  r  rP  N)
r   r   r   r  r   r  r   r   r!   r"   )r   r   r   re  rf  s        rl   r   z6PyiCloudService._authenticate_with_credentials_service  s     )*&
 &

	FL!5DDD:VVV%%j111,,..DIII+ 	F 	F 	F7C.sE::E	Fs   AA& &
B0BBc                    t                               d           | j        j                            d          st          dd          	 | j                            | j         dd          }t                               d           |                                S # t
          $ r t                               d	            w xY w)
z2Checks if the current access token is still valid.zChecking session token validityr  z$Missing X-APPLE-WEBAUTH-TOKEN cookieNz	/validatenull)r   zSession token is still validzInvalid authentication token)	r?   r   r   r  r{   r!   r  r   r   )r   reqs     rl   r   zPyiCloudService._validate_token  s    6777|#''(?@@ 	.6  	 L--'222 .  C LL788888::+ 	 	 	LL7888	s   AB &C	overridesc                    t                                           }|                    | j        | j        | j        d           | j        j                            d          r| j        j        d         |d<   | j        j                            d          r| j        j        d         |d<   | j        j                            d          r| j        j        d         |d<   |r|                    |           |S )zEBuild Apple auth headers for IDMS, bridge, and verification requests.)r   rZ   zX-Apple-Frame-Idscnt
session_idzX-Apple-ID-Session-Idauth_attributeszX-Apple-Auth-Attributes)r\   r   r   r   r   r   r   r{   )r   rv  r   s      rl   rQ  z!PyiCloudService._get_auth_headers  s     #5"9"9";";/'+$(O 	
 	
 	
 <  (( 	8"l/7GFO<  .. 	O/3|/@/NG+,<  !233 	V151BCT1UG-. 	&NN9%%%rn   c                     | j         S )zReturn the session.)r   r   s    rl   r   zPyiCloudService.session  s     }rn   c                 V    | j                             dd          p| j         p| j        S )zDReturn whether the current auth state still requires MFA completion.hsaChallengeRequiredF)r   r{   r  r   r   s    rl   _is_mfa_requiredz PyiCloudService._is_mfa_required  s6     IMM0%88 "**"!	
rn   c                     |                                  o2| j                            di                               dd          dk    S )z4Returns True if two-step authentication is required.r   
hsaVersionr      r~  r   r{   r   s    rl   r  zPyiCloudService.requires_2sa  F     !!## F	h++//a@@AE	
rn   c                     |                                  o2| j                            di                               dd          dk    S )z6Returns True if two-factor authentication is required.r   r  r      r  r   s    rl   r  zPyiCloudService.requires_2fa  r  rn   c                 8    | j                             dd          S )z'Returns True if the session is trusted.hsaTrustedBrowserF)r   r{   r   s    rl   r  z"PyiCloudService.is_trusted_session  s     y}}0%888rn   c                     | j                             | j         d| j                  }|                                                    d          S )z4Returns devices trusted for two-step authentication.z/listDevicesr;  devices)r   r{   r   r   r   )r   requests     rl   trusted_deviceszPyiCloudService.trusted_devices  sQ     !L,,#111$+ - 
 
 ||~~!!),,,rn   devicec                     | j                             | j         d| j        |          }|                                                    dd          S )z>Requests that a verification code is sent to the given device.z/sendVerificationCoder   r"  F)r   r  r   r   r   r{   )r   r  r  s      rl   send_verification_codez&PyiCloudService.send_verification_code  sU     L--#:::; . 
 

 ||~~!!)U333rn   rK   c                 
   |                     |dd           	 | j                            | j         d| j        |           n$# t
          $ r}|j        dk    rY d}~dS  d}~ww xY w|                                  | j         S )z:Verifies a verification code received on a trusted device.T)verificationCoder   z/validateVerificationCoder   i[NF)	r   r   r  r   r   r!   rK   trust_sessionr  )r   r  rK   re  s       rl   validate_verification_codez*PyiCloudService.validate_verification_code  s     	4FFGGG
	L'BBB{     
 , 	 	 	zV##uuuuu		 	$$$s   *A 
A&A! A!!A&c                 P   |                      ddi          }| j                            | j        |          }i }	 |                                }n# t
          t          t          f$ r d}Y nw xY wt          |t                    r*|
                    |           t          j        |          }nt          t          |dd                    }|                                }|
                    |           || _        |                                  |                     d           |S )z,Retrieve auth request options for assertion.rU   z	text/htmlr   NtextrL   r   )rQ  r   r{   r   r   AttributeError	TypeErrorr)  rz   r   r   r)   from_auth_optionsr,   getattras_auth_datar   r  rp  )r   r   r+  auth_optionsresponse_jsonboot_contextboot_auth_datas          rl   r^  z%PyiCloudService._get_mfa_auth_options$  s*    (((K)@AA<##D$7#II')	!$MMOOMM	:6 	! 	! 	! MMM	! mT** 	O...*<\JJLL/&"0M0MNNL%2244N+++".//111++I666s   A A-,A-c                 N   |                      dt          i          }	 | j                            | j         d|           t
                              d           n*# t          $ r t
                              d           Y nw xY w|                                 }|	 | j        	                    | j         d|
                                dd	|
           t
                              d|j                   dS # t          $ r t
                              d           Y dS w xY wdS )a9  Request a 2FA code delivery after SRP authentication requires MFA.

        Apple does not automatically push a verification code for API-based
        (non-browser) sessions after SRP. This method explicitly triggers the
        push notification to trusted devices and falls back to SMS when available.
        rU   z/verify/trusteddevicer  z*Requested 2FA code via trusted device pushz8Could not request 2FA device push; will try SMS fallbackN/verify/phonesmsphoneNumbermoderD  z(Requested 2FA code via SMS (phone id %s)zCould not request 2FA SMS code)rQ  r   r   r{   r   r?   r   	Exception_trusted_phone_numberputr   rr   )r   r   r   s      rl   r_  z!PyiCloudService._request_2fa_code>  s~    (((4E)FGG	UL&===     LLEFFFF 	U 	U 	ULLSTTTTT	U  $99;;+?  *999';'S'S'U'U %  $ !    >(2      ? ? ?=>>>>>>? ,+s$   >A $BBAC: :$D"!D"methodnoticec                 "    || _         || _        dS )zCTrack the active MFA delivery route for the current auth challenge.N)r   r   )r   r  r  s      rl   rp  z.PyiCloudService._set_two_factor_delivery_stateb  s    
 ,2(+1(((rn   c                 b    | j         | j         S t          j        | j                  }|| _         |S )zEReturn the best available HSA2 boot context for the active challenge.)r   r)   r  r   r   r  s     rl   _current_hsa2_boot_contextz*PyiCloudService._current_hsa2_boot_contextj  s6     ".**&8II".rn   c                 v    |                                  }|j        dk    o|j        ot          |j                  S )z>Return whether Apple's HSA2 boot data prefers the bridge flow.zauth/bridge/step)r  auth_initial_routehas_trusted_devicesr~   bridge_initiate_datar  s     rl   _supports_trusted_device_bridgez/PyiCloudService._supports_trusted_device_bridget  sD     6688+/AA 808\677	
rn   c                 ^    |                                  dk    o|                                 duS )z3Return whether SMS delivery is currently available.r  N)_two_factor_moder  r   s    rl   _can_request_sms_2fa_codez)PyiCloudService._can_request_sms_2fa_code~  s6     !!##u, 9**,,D8	
rn   c                 R   |                                  }|st                      |                                dd}|                     dt          i          }| j                            | j         d||           |                                  | 	                    d|           dS )z4Trigger SMS delivery for the current HSA2 challenge.r  r  rU   r  rD  T)
r  r#   r   rQ  r   r   r  r   r  rp  )r   r  r   r   r   s        rl   _request_sms_2fa_codez%PyiCloudService._request_sms_2fa_code  s      $99;;# 	52444 0GGII 
  
 (((4E)FGG"111 	 	
 	
 	

 	//111++E6:::trn   c                     | j         dk    r| j         S | j                            d          s| j        rdS |                                 rdS |                                 dk    rdS dS )zHReturn the current HSA2 delivery method without exposing auth internals.r   fsaChallengesecurity_keytrusted_devicer  )r   r   r{   security_key_namesr  r  r   s    rl   two_factor_delivery_methodz*PyiCloudService.two_factor_delivery_method  s}     +y8833?~.. 	"$2I 	"!>//11 	$##  ""e++5yrn   c                     | j         S )zGReturn an optional user-facing note about the active 2FA delivery path.)r   r   s    rl   two_factor_delivery_noticez*PyiCloudService.two_factor_delivery_notice  s     //rn   c                 6    | j                             d          S )z@Security key names which can be used for the WebAuthn assertion.keyNames)r   r{   r   s    rl   r  z"PyiCloudService.security_key_names  s     "":...rn   r   c                     |                      dt          i          }| j                            | j         d||           dS )z:Submit the WebAuthn assertion response for authentication.rU   z/verify/security/keyrD  NrQ  r   r   r  r   )r   r   r   s      rl   #_submit_webauthn_assertion_responsez3PyiCloudService._submit_webauthn_assertion_response  sW    (((4E)FGG"888tW 	 	
 	
 	
 	
 	
rn   c                 j    | j                             d          }t                              |          S )z>Return Apple's nested phone verification payload when present.phoneNumberVerification)r   r{   r   r   )r   phone_verifications     rl   _phone_number_verificationz*PyiCloudService._phone_number_verification  s0     "_001JKK&334FGGGrn   c                     t                               | j                            d                    }||S |                                                                 S )z;Return the best available trusted phone number description.r   )rq   r   r   r{   r  r   )r   r   s     rl   r  z%PyiCloudService._trusted_phone_number  sZ      2>>O 455 
  
  +''..00JJLLLrn   c                     | j                             d          }t          |t                    r|S |                                 }|dS |j        S )z7Return the current 2FA delivery mode reported by Apple.r  N)r   r{   rz   r}   r  rt   )r   r  r   s      rl   r  z PyiCloudService._two_factor_mode  sV     ""6**dC   	K#99;;'4#--rn   c                 "   | j                             d          s| j        r|                     d           dS |                                  |                                 r	 | j                            | j        | j	        | 
                    dt          i          |                                 t          t          | j        j                            dt           d                                       | _        |                     d           dS # t$          $ rJ t&                              d	d
           |                                 r|                     d          cY S  w xY w|                                 r|                                 S dS )zATrigger the active HSA2 delivery route for the current challenge.r  r  FrU   rD   )r   auth_endpointr   r  
user_agentr  TzKTrusted-device bridge bootstrap failed; falling back to SMS when available.r#  z2Trusted-device prompt failed; falling back to SMS.)r  )r   r{   r  rp  r  r  r   startr   r   rQ  r   r  r   r}   r   rE   r   r'   r?   r   r  r  r   s    rl   request_2fa_codez PyiCloudService.request_2fa_code  s    ?~.. 	$2I 	//???5//111//11 	484O4U4U L"&"5 22H>O3PQQ!%!@!@!B!B#,00x?UVV    5V 	5 	51 334DEEEt7 	 	 	a!     1133 55S 6      	 ))++ 	0--///us   #B+D AE$"E$c                 B    t          t          j                              S )z!List the available FIDO2 devices.)r   r   list_devicesr   s    rl   fido2_deviceszPyiCloudService.fido2_devices  s     M.00111rn   c           
         | j                             di           }	 |d         }|d         }|d         }n"# t          $ r}t          d          |d}~ww xY w|s9t	          t          j                              }|st          d          |d         }t          |t          d	          
          }d |D             }	t          t          |          ||	t          d                    }
|                    |
                              d          }|                     |t!          |j        j                  t!          |j        j                  t!          |j        j                  |j        j        rt!          |j        j                  ndt!          |j                  |d           |                                  dS )zAConduct the WebAuthn assertion ceremony with user's FIDO2 device.r  	challenge
keyHandlesrpIdzMissing WebAuthn challenge dataNzNo FIDO2 devices foundr   zhttps://apple.com)client_data_collectorc                 d    g | ]-}t          t          |          t          d                     .S )z
public-key)rw   type)r   r=   r   )rA  cred_ids     rl   rC  z8PyiCloudService.confirm_security_key.<locals>.<listcomp>#  sO     <
 <
 <
  * ))0G0U0U  <
 <
 <
rn   discouraged)r  rp_idallow_credentialsuser_verification)r  
clientDatasignatureDataauthenticatorData
userHandlecredentialIDr  )r   r{   KeyErrorr!   r   r   r  RuntimeErrorr   r   r   r=   r   get_assertionget_responser  r<   r+  client_data	signatureauthenticator_datauser_handleraw_idr  )r   r  fsar  allowed_credentialsr  re  r  clientcredentialsassertion_optionsresults               rl   confirm_security_keyz$PyiCloudService.confirm_security_key  s   "o11."EE	K(I"%l"3KEE 	 	 	.1 	
  	 +/0J0L0L+M+MG ="#;<<<QZF"<=P"Q"Q
 
 
<
 <
 /	<
 <
 <
 >#I..)9-HH	
 
 
 *0)=)=*
 *

,q// 	 	00&()DEE!+FO,E!F!F%/0R%S%S?.j)DEEE *6= 9 9
 
	
 	
 	
 	s   6 
A AAc                     t                               d           | j                            | j         d| j                                                  }|S )z.Check if the user has consented to PCS access.zQuerying web access statez/requestWebAccessStater;  r?   r   r   r  r   r   r   )r   r  s     rl   _check_pcs_consentz"PyiCloudService._check_pcs_consentC  sW    0111|  #;;;DK ! 
 

$&& 	 rn   app_namederived_from_user_actionc                     t                               d           | j                            | j         d||d| j                                                  S )zESend a request to the PCS endpoint to check the status of PCS access.zQuerying PCS statusz/requestPCS)rr  derivedFromUserAction)r   r   r  )r   r  r  s      rl   _send_pcs_requestz!PyiCloudService._send_pcs_requestL  sg     	*+++|  #000#)A  ; ! 
 
 $&&	rn   c                    |                                  }|                    dd          st                              d           dS |                    dd          syt                              d           | j                            | j         d| j        	                                          }|                    d
          st          d          t                              d           t          t                    D ]{}|                    dd          rt                              d            nHt                              d           t          j        t                     |                                  }|t          t                    D ]}|                     ||dk              }|d         dk    rt                              d            dS |d         dv r;t                              d|d                    t          j        t                     t                              d|d                    t          d          dS )z*Request PCS access for a specific service.isICDRSDisabledFz;Skipping PCS request because Apple reports ICDRS is enabledNisDeviceConsentedForPCSTzRequesting PCS consentz/enableDeviceConsentForPCSr;  isDeviceConsentNotificationSentzUnable to request PCS access!zWaiting for PCS consentzPCS consent grantedz'PCS consent not granted yet, waiting...r   )r  r  r"  zPCS access was grantedmessage)z'Requested the device to upload cookies.z$Cookies not available yet on server.z#PCS access couldn't be obtained: %szUnknown PCS state: %s)r  r{   r?   r   r   r  r   r   r   r!   rangerC   timesleeprA   r  re  )r   r  _check_pcs_respr  _attempts         rl   _request_pcs_for_servicez(PyiCloudService._request_pcs_for_service[  sD   *.*A*A*C*C""#4e<< 	LLVWWWF""#<dCC 	TLL1222<$$'CCCDK %  dff  88=>> T23RSSS.///'' 	8 	8A""#<dCC 2333LLBCCCJ~&&&"5577OO_-- 	T 	TG#'#9#9)0A $: $ $D
 H~**5666I #   BDOTTT
>****4d9oFFF23RSSS%	T 	Trn   c                    | j         }	 | j        dk    r|                     |           n||j        sy| j                            | j        | j        |                     dt          i          ||          s5t                              d           	 ||                                  dS dS n|                     |           nM# t          $ r  t          $ r6 t                              d           Y ||                                  dS dS w xY w||                                  n# ||                                  w w xY wt                              d           |                                  | j         S )zDVerifies a verification code received via Apple's 2FA system (HSA2).r  NrU   )r   r  r   bridge_staterK   zCode verification failed.FzCode verification successful.)r   r  _validate_sms_code#uses_legacy_trusted_device_verifierr   validate_coder   r   rQ  r   r?   re  r  _validate_trusted_device_coder(   r!   r   r  r  )r   rK   r	  s      rl   validate_2fa_codez!PyiCloudService.validate_2fa_code  s   8	:.%77''----($H ) 2@@ L"&"5 22H>O3PQQ!- A   ! LL!<===  '7799999 ('%! 2248889 	 	 	+ 	 	 	LL4555'7799999 ('	
 '77999 '779999 ( 	4555$$$s0   BC +C D& .D0D& DD& &D?c                     dd|ii}|                      dt          i          }| j                            | j         d||           dS )zIVerifies a verification code received via Apple's legacy device endpoint.securityCoderK   rU   z"/verify/trusteddevice/securitycoderD  Nr  )r   rK   r   r   s       rl   r  z-PyiCloudService._validate_trusted_device_code  si     !/?"&"8"8(DU9V"W"W"FFF 	 	
 	
 	
 	
 	
rn   c                 &   |                                  }|st                      |                                d|i|j        pdd}|                     dt
           dt           i          }| j                            | j	         d||           dS )	z=Verifies a verification code received via Apple's SMS system.rK   r  )r  r  r  rU   z, z/verify/phone/securitycoderD  N)
r  r#   r   rt   rQ  r   r   r   r  r   )r   rK   r   r   r   s        rl   r
  z"PyiCloudService._validate_sms_code  s    #99;;# 	52444 0GGII#TN(2;e 
  

 #'"8"8+BB/@BBC#
 #
 	">>> 	 	
 	
 	
 	
 	
rn   c                 F   d| _         |                                 }	 | j                            | j         d|           |                                  t                              d           dS # t          t          f$ r t          
                    d           Y dS w xY w)z9Request session trust to avoid user log in going forward.Fz
/2sv/trustr  zSession trust successful.TzSession trust failed.)r   rQ  r   r{   r   r.  r?   r   r!   r   re  )r   r   s     rl   r  zPyiCloudService.trust_session  s    ""&"8"8":":
	L&222     ))+++LL45554,.JK 	 	 	LL011155	s   AA1 1+B B ws_keyc                     | j         | j                             |          t          d|           | j         |         d         S )z5Get webservice URL, raise an exception if not exists.NzWebservice not available: url)r   r{   r%   )r   r  s     rl   get_webservice_urlz"PyiCloudService.get_webservice_url  sS    $(9(=(=f(E(E(M65V55    (//rn   c                     | j         sl	 |                     d          }t          || j        | j        | j        | j        | j                  | _         n"# t          $ r}t          d          |d}~ww xY w| j         S )zReturns all devices.findme)service_roottoken_endpointr   r   r   r   z$Find My iPhone service not availableN)
r   r  r2   r   r   r   r   r   r%   r&   r   r  re  s      rl   r  zPyiCloudService.devices  s     } 	$($;$;H$E$E :!-#'#7 L; $ 1%)%;! ! ! 8   0:  }s   AA 
A1A,,A1c                     | j         sZ|                     d          }	 t          || j        | j                  | _         n"# t
          $ r}t          d          |d}~ww xY w| j         S )zGets the 'HME' service.premiummailsettingsr  r   r   z#Hide My Email service not availableN)r   r  r3   r   r   r!   r&   r  s      rl   hidemyemailzPyiCloudService.hidemyemail  s       	 $ 7 78M N NL	$6!- L;% % %!!
 0   09       !A   
A
AAc                     | j         d         S )zReturns the iPhone.r   )r  r   s    rl   iphonezPyiCloudService.iphone  s     |Arn   c                     | j         s`|                     d          }	 t          || j        | j        | j                  | _         n"# t          $ r}t          d          |d}~ww xY w| j         S )zGets the 'Account' service.account)r  r   r   r   zAccount service not availableN)r   r  r-   r   r   r   r!   r&   r  s      rl   r$  zPyiCloudService.account  s     } 	 $ 7 7	 B BL
 .!- L#'#:;	! ! ! 0   03  }s   'A 
A%A  A%c                    | j         su|                     d          }	 t          || j        | j                  | _         n=# t
          $ r0}d|j        k    rt          d          |t          d          |d}~ww xY w| j         S )zGets the 'File' service.ubiquityr  zAccount migratedz3Files service not available use `api.drive` insteadzFiles service not availableN)r   r  r6   r   r   r!   reasonr&   r  s      rl   fileszPyiCloudService.files&  s     { 	 $ 7 7
 C CL-!- L;  
 0   %554M  ! 11  {s   !A   
A:
+A55A:c                    |                      d           | j        s|                     d          }|                     d          }|                     d          }| j        d         d         | j        d<   	 t          || j        | j        ||          | _        n"# t          $ r}t          d          |d	}~ww xY w| j        S )
zGets the 'Photo' service.photosckdatabasewsuploadimagewssharedstreamsr   r
  )r  r   r   
upload_urlshared_streams_urlzPhotos service not availableN)	r  r   r  r   r   r4   r   r!   r&   )r   r  r.  r/  re  s        rl   r*  zPyiCloudService.photos;  s     	%%h///| 	 $ 7 7 G GL"55oFFJ&*&=&=o&N&N"&)H"5f"=DK,!- L;)'9      0   02  |s   8#B 
B;&B66B;c                     | j         sZ|                     d          }	 t          || j        | j                  | _         n"# t
          $ r}t          d          |d}~ww xY w| j         S )zGets the 'Calendar' service.calendarr  zCalendar service not availableN)r   r  r/   r   r   r!   r&   r  s      rl   r1  zPyiCloudService.calendarT       ~ 		 $ 7 7
 C CL!0!-t|DK" " " 0   04  ~r   c                     | j         sZ|                     d          }	 t          || j        | j                  | _         n"# t
          $ r}t          d          |d}~ww xY w| j         S )zGets the 'Contacts' service.contactsr  zContacts service not availableN)r   r  r0   r   r   r!   r&   r  s      rl   r4  zPyiCloudService.contactsc  r2  r   c                     | j         s`	 |                     d          }t          || j        | j        | j                  | _         n"# t          $ r}t          d          |d}~ww xY w| j         S )zGets the 'Reminders' service.r+  r  r   r   r   zReminders service not availableN)r   r  r5   r   r   r   r!   r&   r  s      rl   	reminderszPyiCloudService.remindersr  s      	$($;$;N$K$K"2!- L;.2.M	# # # 0   05     <A 
A%A  A%c                     |                      d           | j        sl	 t          |                     d          |                     d          | j        | j                  | _        n"# t          $ r}t          d          |d}~ww xY w| j        S )zGets the 'Drive' service.iclouddrivedrivewsdocws)r  document_rootr   r   zDrive service not availableN)r  r   r1   r  r   r   r!   r&   )r   re  s     rl   drivezPyiCloudService.drive  s     	%%m444{ 	
*!%!8!8!C!C"&"9"9'"B"B L;	   0   01  {s   AA' '
B1BBc                     | j         s`	 |                     d          }t          || j        | j        | j                  | _         n"# t          $ r}t          d          |d}~ww xY w| j         S )zGets the 'Notes' service.r+  r6  zNotes service not availableN)r   r  r8   r   r   r   r!   r&   r  s      rl   noteszPyiCloudService.notes  s     { 	$($;$;N$K$K*!- L;.2.M	   0   01  {r8  c                     | j         sg	 |                     d          }t          || j        | j        | j                  | _         n)# t          t          f$ r}t          d          |d}~ww xY w| j         S )zGets the 'Invites' service.r+  r6  zInvites service not availableN)	r   r  r7   r   r   r   r!   r%   r&   r  s      rl   inviteszPyiCloudService.invites  s     } 	$($;$;N$K$K .!- L;.2.M	! ! ! -4    13 	 }s   <A A,A''A,c                     | j         S )z8Retrieves the account name associated with the Apple ID.)r   r   s    rl   r   zPyiCloudService.account_name  s     ~rn   c                     d| j          S )z4Return a concise human-readable service description.ziCloud API: )r   r   s    rl   __str__zPyiCloudService.__str__  s    1d/111rn   c                     d|  dS )z.Mirror ``__str__`` for interactive inspection.<>r   r   s    rl   __repr__zPyiCloudService.__repr__  s    4{{{rn   )rc   NN)NNTNTNFN)FN)ar   r   r   r   r   r   r}   r   r~   floatr   r   propertyr   r   r   r  r   r  r  r
   r  r,  r   r/  r.  r   r   rQ  r9   r   r~  r  r  r  r   r  r  r  r   r^  r_  rp  r)   r  r  r  r  r  r  r   r  r  r   r  rq   r  r  r  r   r  r  r  r  r  r  r  r
  r  r  r2   r  r3   r  r.   r"  r-   r$  r6   r(  r4   r*  r/   r1  r0   r4  r5   r7  r1   r>  r8   r@  r7   rB  r   rE  rI  r   rn   rl   r   r      s`
        Y Y Y Y! ! !QT ! ! ! !$ #'*.#' )-")-Y  "AEY  Y  Y Y  3-Y  #3-	Y 
 Y  C=Y  Y  !Y  Y   $,Y  Y  $,,=#>Y  
Y  Y  Y  Y v '4 ' ' ' X' EI) )!)4<SM)	) ) ) )V $t  $  $  $  $  $D
9 
9 
9 
9& & & &.1 1 1 1!c3h ! ! ! !L #"$('
 '
 '
 '
 	'

 "'
 
c3h'
 '
 '
 '
R, , , ,]? ]? ]? ]?~F F F FBFhsm FPT F F F F$    $ 59 !$sCx.1	c3h   4     X
$ 
 
 
 
 
d 
 
 
 X
 
d 
 
 
 X
 9D 9 9 9 X9 -d38n!5 - - - X-4T#s(^ 4 4 4 4 4%c3h %s %t % % % %*t    4"? "? "? "?J 482 22#+C=2	2 2 2 2O    
 
 
 
 

4 
 
 
 
 HSM T    , C    X" 0HSM 0 0 0 X0
 /HT#Y$7 / / / X/
 
 
 
 
 
H,C H H H H	Mx0B'C 	M 	M 	M 	M.(3- . . . .%$ % % % %N 2tM2 2 2 2 X26 68M+B 6d 6 6 6 6pDcN    7;	c3h   .T .T .T .T .T .T`"%c "%d "% "% "% "%H	
# 	
$ 	
 	
 	
 	

s 
t 
 
 
 
*t    $0 0 0 0 0 0 3    X& !/ ! ! ! X!      X     X"     X(     X0 /    X /    X +    X" |    X$ |    X"     X( c    X
2 2 2 2 2#      rn   r   rJ  )ir   rY  ri   r   loggingr  dataclassesr   osr   r   r   r   r   tempfiler	   typingr
   r   r   r   r   r   uuidr   rR  fido2.clientr   r   	fido2.hidr   fido2.webauthnr   r   r   r   r   requestsr   requests.modelsr   pyicloud.common.cloudkit.baser   pyicloud.constr   r   r   pyicloud.exceptionsr   r    r!   r"   r#   r$   r%   r&   r'   r(   pyicloud.hsa2_bridger)   r*   r+   r,   pyicloud.servicesr-   r.   r/   r0   r1   r2   r3   r4   r5   r6   pyicloud.services.invitesr7   pyicloud.services.notesr8   pyicloud.sessionr9   pyicloud.srp_passwordr:   r;   pyicloud.utilsr<   r=   r>   	getLoggerr   r?   Loggerr   rA   r|   rC   rE   r   r}   r(  r\   ra   rm   rq   r   r   r   rn   rl   <module>rd     sV           ! ! ! ! ! ! 4 4 4 4 4 4 4 4 4 4 4 4 4 4       ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;       



 @ @ @ @ @ @ @ @ # # # # # #                    $ $ $ $ $ $ ; ; ; ; ; ; M M M M M M M M M M                                                          5 4 4 4 4 4 0 0 0 0 0 0 , , , , , , > > > > > > > >          +*844 4 4 4       	R$sCx.    #555%a!1":(.#0#)\(dj,'	
 	
 	 	 	& & DcN   0 )& c3h   0 0x} 0 0 0 0 0 $' ' ' ' ' ' ' 'T $' ' ' ' ' ' ' 'T} } } } } } } } } }rn   