a
    hf                     @   sD  d dl Z d dlZd dlZd dlZd dl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 ddlmZ ddl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 dd	lmZm Z  G d
d deZ!G dd de!Z"G dd de!Z#G dd de!Z$G dd de!Z%G dd de%Z&G dd de%Z'G dd de!Z(dS )    N   )InfoExtractor)NaverBaseIE)	YoutubeIE   )	HTTPError)
ExtractorErrorUserNotLivefloat_or_noneint_or_nonejoin_nonemptyjwt_decode_hs256str_or_nonetry_callupdate_url_queryurl_or_none)requiretraverse_objc                   @   s   e Zd ZdZdZdZdZdZdZdZ	dd	d
dZ
dZddddddZdZi ZdZedd Zejdd Zedd Zdd Zdd Zdd  Zd!d" Zd#d$ Zd%d& Zd'd( Zd:d*d+Zd,d- Zd.d/ Zd0d1 Z d2d3 Z!d4d5 Z"d6d7 Z#d8d9 Z$dS );WeverseBaseIEweversezhttps://accountapi.weverse.ioZWEBs(   1b9cb6378d959b45714bec49971ade22e6e24e42Zwe2_access_tokenZwe2_refresh_tokenZwe2_device_idapplication/jsonzhttps://weverse.iohttps://weverse.io/)AcceptZOriginZReferera  You can log in using your refresh token with --username "{}" --password "REFRESH_TOKEN" (replace REFRESH_TOKEN with the actual value of the "{}" cookie found in your web browser). You can add an optional username suffix, e.g. --username "{}" , if you need to manage multiple accounts. z4This content is only available for logged-in users. z5"{}" is not valid login username for this extractor. zxYour password is not a valid refresh token. Make sure that you are passing the refresh token, and NOT the access token. zYour access token has expired and there is no refresh token available. Refresh your session/cookies in the web browser and try again. zYour refresh token has expired. Log in to the site again using your web browser to get a new refresh token or export fresh cookies. )login_requiredinvalid_usernameinvalid_passwordno_refresh_tokenexpired_refresh_tokenZoauthNc                 C   s   i | j ddtt dS )NZ 5419526f1c624b38b10787e5c10b2a7ar   )zX-ACC-APP-SECRETzX-ACC-SERVICE-IDzX-ACC-TRACE-ID)_API_HEADERSstruuiduuid4self r$   M/mnt/pikpak/tmp/myenv/lib/python3.9/site-packages/yt_dlp/extractor/weverse.py_oauth_headersB   s    
zWeverseBaseIE._oauth_headersc                 C   s*   |   d }|sdS t| j|dd S )Nr   cookies+r   )_get_login_infor   _OAUTH_PREFIX	partition)r#   usernamer$   r$   r%   _oauth_cache_keyK   s    zWeverseBaseIE._oauth_cache_keyc                 C   s   t | j| jS N)bool_oauth_tokensget_ACCESS_TOKEN_KEYr"   r$   r$   r%   _is_logged_inR   s    zWeverseBaseIE._is_logged_inc                 C   sR   | j | j dd dddi | jdd| j| j  id}t|dthfd	d
dkS )Nz/api/v1/token/validatezValidating access tokenzUnable to valid access token  AuthorizationBearer )Zexpected_statusheadersZ	expiresInr   default<   )_download_json_ACCOUNT_API_BASEr&   r0   r2   r   int)r#   responser$   r$   r%   _access_token_is_validV   s    z$WeverseBaseIE._access_token_is_validc                 C   s:   t | j| d t  dk }|| jks,|s0|S |   S )Nexpi  )r   r0   time_REFRESH_TOKEN_KEYr?   )r#   key
is_expiredr$   r$   r%   _token_is_expired`   s    zWeverseBaseIE._token_is_expiredc                 C   s  | j | js| d | | jr.| d ddi}| jrRd| j | j  |d< zF| j| j dd dd	i | j	|t
jd
| j | j idd d}W n ty< } zt|jtr&|jjdkr&| j   | jdkr| jjdd| jd | jjdd| jd n| j| j| j| j  | d  W Y d }~n
d }~0 0 | j t|| jdthtdhf| jd
thtdhfi | jdkr| d| j| j | j  | d| j| j | j  n| j| j| j| j  d S )Nr   r   Content-Typer   r6   r5   z/api/v1/token/refreshzRefreshing access tokenzUnable to refresh access tokenZrefreshToken),:)
separators)r7   datar4   r'   z.weverse.io/)domainpathnameZaccessTokenzaccess tokenzrefresh token)r0   r1   rB   _report_login_errorrE   r3   r2   r;   r<   r&   jsondumpsencoder   
isinstancecauser   statusclearr-   	cookiejarcachestore_NETRC_MACHINEupdater   r   r   Z_set_cookie)r#   r7   r>   er$   r$   r%   _refresh_access_tokenf   sF    





z#WeverseBaseIE._refresh_access_tokenc                 C   s4   | j s
i S | | jr|   dd| j| j  iS )Nr5   r6   )r3   rE   r2   r]   r0   r"   r$   r$   r%   _get_authorization_header   s
    z'WeverseBaseIE._get_authorization_headerc                 C   s   | j | }|  d }|dkr:||}| j d| }n|sJ| j d}tt|| j| j| j|d| jdddd  d	d
ddd S )Nr   r   r(   z	+USERNAMEzOr else you can uZsession_cookies)methodr    )delimTexpected)	_LOGIN_ERRORS_MAPr)   formatr*   r   r   _LOGIN_HINT_TMPLrB   Z_login_hint)r#   Zerror_id	error_msgr,   r$   r$   r%   rO      s    

z!WeverseBaseIE._report_login_errorc                    s   | j r
d S |dd | jkr(| d | j| jj| j| j	i d | j rX| 
 rXd S | j}| j|rt| |rt fdddkr| d  | j|< |   d S )	Nr(   r   r   r8   c                      s   t  d S )NZscope)r   r$   passwordr$   r%   <lambda>       z.WeverseBaseIE._perform_login.<locals>.<lambda>Zrefreshr   )r3   r+   r*   rO   r0   r[   rX   loadrZ   r-   r?   rB   r1   rE   r   r]   )r#   r,   ri   Zrt_keyr$   rh   r%   _perform_login   s    


zWeverseBaseIE._perform_loginc              	   C   s|   |  d}| js.t|| jdfp*tt | _| jr8d S | j	t|| j
| j
df| j| jdfi | jrx|  sx|   d S )Nr   value)Z_get_cookies
_device_idr   _DEVICE_ID_KEYr   r    r!   r3   r0   r[   r2   rB   r?   r]   )r#   r'   r$   r$   r%   _real_initialize   s    

zWeverseBaseIE._real_initializeDownloading API JSONc           
      C   sV  t |dd| j| jdd}dD ]0}tt d }ttj| j|d d  | 	 t
jd  }zP| jd	| |||i | j|  |rd
dini d| ji||ddW   S  tyN }	 zz|st|	jts nX| jr|	jjdkr|   W Y d }	~	qn,|	jjdkr8| jr.tddd| d  W Y d }	~	qd }	~	0 0 qd S )NZ be4d79eb8fc7bd008ee82c8ec4ff6fd4enZpc)ZappIdlanguageosplatformZwpf)FT     )	digestmodz,https://global.apis.naver.com/weverse/wevwebrF   r   zWEV-device-Id)wmsgpadwmd)noterJ   r7   queryr4   i  z1Your account does not have access to this contentTrb   r   )r   _CLIENT_PLATFORMr=   rA   base64	b64encodehmacHMAC_SIGNING_KEYrR   hashlibsha1digestdecoder;   r   r^   ro   r   rS   rT   r   r3   rU   r]   rO   )
r#   epvideo_idrJ   r|   Zapi_pathis_retryrz   r{   r\   r$   r$   r%   	_call_api   sT    

zWeverseBaseIE._call_apic                 C   s&   | j r
dnd}| d| | d|S )Nr`   z/previewz/post/v1.0/post-z?fieldSet=postV1)r3   r   )r#   r   rM   r$   r$   r%   _call_post_api   s    zWeverseBaseIE._call_post_apic                 C   s   t | jd| |ddd S )Nz>/community/v1.0/communityIdUrlPathByUrlPathArtistCode?keyword=zFetching community IDr|   communityId)r   r   )r#   channelr$   r$   r%   _get_community_id   s    zWeverseBaseIE._get_community_idc           	      C   s   t |dddd dddthfddthfd	d
dthfd
dthfdthfddthfdf}t |ddd fD ]}i }t |ddd fD ]}|dd||d < q| j|d |ddd|d}|r|D ]&}t|d ||d< tj||d< q|	| qh|S )NZvideoslistc                 S   s   t |d S )Nsourcer   _vr$   r$   r%   rj      rk   z,WeverseBaseIE._get_formats.<locals>.<lambda>r   ZencodingOptionwidthheighttypeZbitratevideoaudiosizeid)urlr   r   ZvcodecZvbrZabrfilesizeZ	format_idZstreamsc                 S   s   |d dkot |d S )Nr   HLSr   r   r   r$   r$   r%   rj     rk   keysc                 S   s   |d dko|d S )Nr   paramrN   r$   r   r$   r$   r%   rj     rk   rn   r`   rN   mp4hlsF)m3u8_idfatalr}   r   Zextra_param_to_segment_url)
r   r   r   r1   Z_extract_m3u8_formatsr   urllibparse	urlencodeextend)	r#   rJ   r   formatsstreamr}   r   Zfmtsfmtr$   r$   r%   _get_formats   s.    





zWeverseBaseIE._get_formatsc                    s6   d  fdd}t  |r0||d||dgS |gS )Nz\.(?:ttml|vtt)c                    s   t  || S r.   )resub)xyZsubs_ext_rer$   r%   rj     rk   z)WeverseBaseIE._get_subs.<locals>.<lambda>z.ttmlz.vtt)r   search)r#   Zcaption_urlZreplace_extr$   r   r%   	_get_subs  s
    zWeverseBaseIE._get_subsc                 C   s   t |dthfdthfddthfddthfddthtfdd	thfd
ddthfdtddhfd
ddtddhfd
dthfd
ddthfd
ddthfdthfdddS )N))	extension	mediaInfotitler   ))r   r   bodyr   authorprofileNameZmemberId	communitycommunityNamer   r   r   r   r   playTimepublishedAtrw   ZscaleZonAirStartAt))r   	thumbnailr   )r   ZthumbZ	playCountZ	likeCountcommentCount)r   descriptionuploaderuploader_idcreators
channel_idduration	timestamprelease_timestampr   
view_count
like_countcomment_countFget_all)r   r   allr   r
   r   r   )r#   metadatar$   r$   r%   _parse_post_meta  s"    



zWeverseBaseIE._parse_post_metac              	   C   s.   | j f i t|ddddfdtdddiS )	N))r   r   NZpaidmembershipOnly)Zneeds_premiumZneeds_subscriptionF)r   expected_type
needs_authT)Z_availabilityr   r/   r#   rJ   r$   r$   r%   _extract_availability+  s    z#WeverseBaseIE._extract_availabilityc                 C   sZ   t |ddthfpi }|ddkrHt ddddd|d	thfpFdS |d
rVdS dS )Nr   r   r   ZLIVEis_live	post_liveis_upcoming)ZONAIRZDONEZSTANDBYZDELAYrU   Z	liveToVodwas_livenot_live)r   dictr1   r   r   r$   r$   r%   _extract_live_status1  s    z"WeverseBaseIE._extract_live_status)Nrr   )%__name__
__module____qualname__rZ   r<   r~   r   r2   rB   rp   r   rf   rd   r*   r0   ro   propertyr&   	functoolscached_propertyr-   r3   r?   rE   r]   r^   rO   rm   rq   r   r   r   r   r   r   r   r   r$   r$   r$   r%   r      sV   



(
*r   c                "   @   s   e Zd ZdZdddddddd	d
dddgddddddeeeddddddddddddddddgd d!d d!d"deeeddd#d#d#d#d#d#d#d#d#d$	d%dd&d'dd(dd)d*d+d,d-d)d.d/d0d/deeedd1d2d3d4gZd5d6 Zd7S )8	WeverseIEzJhttps?://(?:www\.|m\.)?weverse\.io/(?P<artist>[^/?#]+)/live/(?P<id>[\d-]+)z+https://weverse.io/billlie/live/0-107323480Z 1fa849f00181eef9100d3c8254c47979z0-107323480r   u   행복한 평이루💜r`   BilllieZ 5ae14aed7b7cdc65fa87c41fe06cc936billlie72https://weverse.io/billliei.$QcZ20221020i*$Qci  re:^https?://.*\.jpe?g$r   r   r   extr   r   r   r   r   r   channel_urlr   r   upload_dater   release_dater   r   r   r   r   availabilitylive_statusr   md5	info_dictz.https://weverse.io/lesserafim/live/2-102331763Z e46125c08b13a6c8c1f4565035cca987z2-102331763u   🎂김채원 생신🎂zLE SSERAFIM Z d26ddc1e258488a0a2b795218d14d59d
lesserafim47https://weverse.io/lesserafimLE SSERAFIMi8bZ20220801i  zcount:2)	id_IDen_USes_ESvi_VNth_THzh_CNzh_TWja_JPko_KR)r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   Z	subtitlesz,https://weverse.io/treasure/live/2-117230416z2-117230416u+   re:스껄도려님 첫 스무살 생파🦋ZTREASUREZ 77eabbc449ca37f7970054a136f60082ZtreasureZ20zhttps://weverse.io/treasurei,d20230405i,dr   )r   r   r   r   r   r   r   r   r   creatorr   r   r   r   r   r   r   r   r   r   Livestream has endedr   r   skipc                 C   s  |  |dd\}}| |}|d d d }| |}| |}i g  }}	|dkrh| jddd	 nz|d
kr| jd| d|dd}| |d |}
t|
ddd dt	hfdd}| j
||dddd\}	}n|dkr|dv r| | | jddd	 n|d d d }| jd| d|dd d!d" }| jd#| |d$|t|d%pHd&tt tt d' d(|d)rrd*nd+d+d+d,d-d,d.d/d0d1}| ||}	t|d2d3d4tjhfd5k}|r|	r| d6| n|r|	s| | ||d7| |	||d8| |t|| jS )9Nartistr   r   r   videoIdr   zLivestream has not yet startedTrb   r   z/video/v1.3/lives/z0/playInfo?preview.format=json&preview.version=v2Downloading live JSONr   ZlipPlaybackmediac                 S   s   |d dkS )Nprotocolr   r$   r   r$   r$   r%   rj     rk   z)WeverseIE._real_extract.<locals>.<lambda>rM   Fr   r   r   )r   liver   )Zpremium_onlyZsubscriber_onlyz:Livestream has ended and downloadable VOD is not availableZinfraVideoIdz/video/v1.1/vod/z/inKey?preview=falses   {}zDownloading VOD API key)rJ   r|   ZinKeyz:https://global.apis.naver.com/rmcnmv/rmcnmv/vod/play/v2.0/zDownloading VOD JSON)r   r   Z	serviceIdZ2070rw   Zhtml5_pcr   YNrs   prodz[{"adSystem":"null"}]rK   )rC   ZsidpidnonceZdevtZprvZaupZstpbZcplenvlcZadiZadu)r|   r}   metaproviderrN   ZdrmzIRequested content is DRM-protected, only a 30-second preview is availabler   )r   r   r   r   r   r   )_match_valid_urlgroupr   r   r   raise_no_formatsr   Z_parse_jsonr   r   Z#_extract_m3u8_formats_and_subtitlesZ
report_drmr;   r   r    r!   r=   rA   r1   r   lowerZreport_warningr   r   process_subtitlesr   )r#   r   r   r   postapi_video_idr   r   
video_infor   ZplaybackZm3u8_urlr   Zinfra_video_idZin_keyZhas_drmr$   r$   r%   _real_extract  s    








zWeverseIE._real_extractNr   r   r   
_VALID_URLr=   _TESTSr  r$   r$   r$   r%   r   =  s   &Zr   c                   @   s   e Zd ZdZdddddddd	dd
ddddddddeeeeddgdddddddddddddd d!d"dgd#d$d#d$d%d&eeed'dd(d)gZd*d+ Zd,S )-WeverseMediaIEzKhttps?://(?:www\.|m\.)?weverse\.io/(?P<artist>[^/?#]+)/media/(?P<id>[\d-]+)z,https://weverse.io/billlie/media/4-116372884ze-C9wLSQs6or   z5Billlie | 'EUNOIA' Performance Video (heartbeat ver.)z$md5:6181caaf2a2397bca913ffe368c104e5r   ZUCyc9sUCxELTDK9vELO5Fzegz8https://www.youtube.com/channel/UCyc9sUCxELTDK9vELO5Fzegz@Billliez https://www.youtube.com/@BilllieZ20230403i*d   r   Tr   publicz4https://i.ytimg.com/vi/e-C9wLSQs6o/maxresdefault.jpgZEntertainmentzcount:7z	count:100)r   r   r   r   r   r   r   r   r   Zuploader_urlr   r   r   Z	age_limitZplayable_in_embedr   r   r   r   r   Zchannel_follower_countr   
categoriestagsZchannel_is_verifiedZheatmap)r   r   z,https://weverse.io/billlie/media/3-102914520Z 031551fcbd716bc4f080cb6174a43d8az3-102914520u   From. SUHYEON🌸u.   Billlie 멤버별 독점 영상 공개💙💜ZBilllie_officialZ f569c6e92f7eaffef0a395037dcaa54fr   r   r   i0cZ20220903g      1@r   r   r   r   c                 C   s   |  |dd\}}| |}t|dddtjhf}t|dddthf}|dkrj| d	| d
| tS |dkr|r| |tS |dkr| j	ddd n|rt
d| d| 	d d S )Nr  r   r   r   Z	mediaTypeyoutubeZyoutubeVideoIdZvodr   /live/imagez!No video content found in webpageTrb   zUnsupported media type "")r  r  r   r   r   r  
url_resultr   r   r  r   )r#   r   r   r   r  
media_typeZ
youtube_idr$   r$   r%   r    s    
zWeverseMediaIE._real_extractNr   r$   r$   r$   r%   r#    sr   :r#  c                   @   sH   e Zd ZdZdddddddd	d
dgddddeedddgZdd ZdS )WeverseMomentIEzchttps?://(?:www\.|m\.)?weverse\.io/(?P<artist>[^/?#]+)/moment/(?P<uid>[\da-f]+)/post/(?P<id>[\d-]+)zXhttps://weverse.io/secretnumber/moment/66a07e164b56a696ee71c99315ffe27b/post/1-117229444Z 87733ac19a54081b7dfc2442036d282bz1-117229444r   u.   今日もめっちゃいい天気☀️🌤️u   레아Z 66a07e164b56a696ee71c99315ffe27bZsecretnumberZ56zSECRET NUMBER
   r  i,dr   r   )r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   c                 C   s   |  |ddd\}}}| |}|d d d d }| jd| d	| |d
dd }|||| ||| |dt|dthfddthfddthfddthfdddddt	hfdt
ddhfdddddthfdt
hfdt
hfd	ddt|| jS )Nr  uidr   r   momentr   r	  z/cvideo/v1.0/cvideo-z/playInfo?videoId=zDownloading moment JSONr   ZplayInfo)r   r   r   r   r   ))r   r1  r   r   r   r   r   r   r   Z
uploadInfor   r   rw   r   ZimageUrlZemotionCountr   )	r   r   r  r   r   r   r   r   r   Fr   )r  r  r   r   r   r   r   r   r   r
   r   r   r   r  r   )r#   r   r   r   r   r  r  r  r$   r$   r%   r  G  s>    





zWeverseMomentIE._real_extractNr   r$   r$   r$   r%   r.  /  s*   r.  c                   @   s,   e Zd ZdZdZi ZdZdd Zdd ZdS )WeverseTabBaseIENc                 c   s   | j  }tdD ]}|dkr$|n(| jt| j| ||d| j d| d}t|ddd fD ]`}| j	d| d	| j d	|d
  | j
|d
 fi | ||d| | || |dV  q`t|dddthf|d< |d s qqd S )Nr   Downloading z
 tab page r   rJ   c                 S   s   |d S )NpostIdr$   r   r$   r$   r%   rj   r  rk   z+WeverseTabBaseIE._entries.<locals>.<lambda>r   rK   r4  )r   r   r   r   ZpagingZ
nextParamsafter)_QUERYcopy	itertoolscountr   r   	_ENDPOINT_PATHr   r,  
_RESULT_IEr   r   r   r   )r#   r   r   
first_pager}   pageZpostsr  r$   r$   r%   _entriesj  s(    


zWeverseTabBaseIE._entriesc                 C   s   |  |}| |}| jt| j| | j|d| j dd}| j| |||| d| j fi t	|ddddt
hfd	d
thfdfddS )Nr3  z tab page 1r   -rJ   .r   r   r   ZprofileImageUrl)Zplaylist_titler   Fr   )	_match_idr   r   r   r:  r6  r;  Zplaylist_resultr?  r   r   r   )r#   r   r   r   r=  r$   r$   r%   r  ~  s     



zWeverseTabBaseIE._real_extract)	r   r   r   r:  r;  r6  r<  r?  r  r$   r$   r$   r%   r2  d  s   r2  c                   @   s:   e Zd ZdZdddddddgZd	Zd
ZddiZeZ	dS )WeverseLiveTabIEzChttps?://(?:www\.|m\.)?weverse\.io/(?P<id>[^/?#]+)/live/?(?:[?#]|$)z https://weverse.io/billlie/live/7   zbilllie-liver   r   r   r   r   r   Zplaylist_mincountr   z$/post/v1.0/community-%s/liveTabPostsr  fieldSetpostsV1N)
r   r   r   r!  r"  r:  r;  r6  r   r<  r$   r$   r$   r%   rB    s   
rB  c                   @   sL   e Zd ZdZdddddddd	d
ddd
dgZdZdZdddZeZ	dS )WeverseMediaTabIEzRhttps?://(?:www\.|m\.)?weverse\.io/(?P<id>[^/?#]+)/media(?:/|/all|/new)?(?:[?#]|$)z!https://weverse.io/billlie/media/   zbilllie-mediar   r   rD  rE  z'https://weverse.io/lesserafim/media/allTr   Zonly_matchingz'https://weverse.io/lesserafim/media/newz/media/v1.0/community-%s/morer  rG  ZRECENT)rF  Z
filterTypeN)
r   r   r   r!  r"  r:  r;  r6  r#  r<  r$   r$   r$   r%   rH    s&   	
rH  c                   @   s   e Zd ZdZdddddddd	d
ddgdddddeeedddddddddddddddgdddd dd!eeeddd"ddd#d$d%gZd&d' Zd(S ))WeverseLiveIEz>https?://(?:www\.|m\.)?weverse\.io/(?P<id>[^/?#]+)/?(?:[?#]|$)zhttps://weverse.io/purplekissz3-116560493r   u   re:모하냥🫶🏻u   내일은 금요일~><u   채인Z 1ffb1d9d904d6b3db2783f876eb9229dZ
purplekissZ35zPURPLE KISSi\.dZ20230406iS.dz6https://weverse-live.pstatic.net/v1.0/live/62044/thumbr   r   )r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r   z4-181521628u   re:심심해서요r`   u
   채채🤎Z d49b8b06f3cc1d92d655b25ab27ac2e7r   r   r   igZ20241010igz)re:https://phinf\.wevpstatic\.net/.+\.png)r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   zhttps://weverse.io/billlie/TrJ  c                    s|     |} |}t jtd| dddd|dddd	 fd
ddthfdd}|sdt|d d| d| tS )Nz/post/v1.0/community-z/liveTabtruezXonAirLivePosts.fieldSet(postsV1).limit(10),reservedLivePosts.fieldSet(postsV1).limit(10))ZdebugMessagefieldsr
  r   )ZonAirLivePostsZreservedLivePostsrJ   c                    s     |dv S )N)r   r   )r   r   r"   r$   r%   rj     rk   z-WeverseLiveIE._real_extract.<locals>.<lambda>r4  Fr   )r   r   r)  )	rA  r   r   r   r   r   r	   r,  r   )r#   r   r   r   r   r$   r"   r%   r    s"    

	
zWeverseLiveIE._real_extractNr   r$   r$   r$   r%   rK    sl   7rK  ))r   r   r   r   r8  rP   r   rA   urllib.parser   r    commonr   Znaverr   r(  r   Znetworking.exceptionsr   utilsr   r	   r
   r   r   r   r   r   r   r   Zutils.traversalr   r   r   r   r#  r.  r2  rB  rH  rK  r$   r$   r$   r%   <module>   s4   0    %N5*