+
    #-j '                       R t ^ RIHt ^ RIt^ RIt^ RIt^ RIHt ^ RIH	t	 ^ RI
Ht ^ RIHt ^ RIHtHtHt ^ RItRtR	t]P*                  ! 4       R
,          t]	 ! R R4      4       t]3R R llt]3R R lltR tR R ltR(R R lltR R lt]^ RRR3R R lltR)R R lltR R  lt R! R" lt!R# R$ lt"R*R% R& llt#]$R'8X  d   ]PJ                  ! ]#! 4       4       R# R# )+a&  Export saved NYT Cooking Recipe Box URLs and import them into the vault.

This uses a persistent Playwright browser profile so Mitch can log into NYT once.
After that, the script reads rendered Recipe Box pages, extracts recipe links, and
passes each URL through ``recipes.import_web_recipe``.
)annotationsN)suppress)	dataclass)Path)sleep)urljoinurlparse
urlunparsez*https://cooking.nytimes.com/recipe-box/allzhttps://cooking.nytimes.comz.nyt-cooking-playwrightc                  :    ] tR t^t$ R]R&   R]R&   RtR]R&   RtR# )ImportResultstrurlstatus note N)__name__
__module____qualname____firstlineno____annotations__r   __static_attributes__r       D/Users/mitch_tango/dev/rabbit-r1-livekit/agent/src/nyt_recipe_box.pyr   r      s    	HKD#Nr   r   c               $    V ^8  d   QhRRRRRR/# )   hrefr   base_urlreturnz
str | Noner   )formats   "r   __annotate__r    !   s!      s c Z r   c           	     p   V '       g   R# \        \        W4      4      pVP                  R9  d   R# VP                  P	                  R4       Uu. uF  q3'       g   K  VNK  	  pp\        V4      ^8  g   V^ ,          R8w  d   R# \        RRRRP                  VR,          4      ,           RRR34      # u upi )	zHReturn a canonical NYT Cooking recipe URL, or None for non-recipe links.Ncooking.nytimes.com/recipeshttps:Nr   Nr   >   r"   www.cooking.nytimes.com)r   r   netlocpathsplitlenr	   join)r   r   parsedppartss   &&   r   canonical_recipe_urlr/   !   s    gh-.F}}NN))#.4.1!QQ.E4
5zA~qY.	'sxxb	/B)BBBO  5s   	B3B3c               $    V ^8  d   QhRRRRRR/# )r   hrefs	list[str]r   r   r   r   )r   s   "r   r    r    0   s!     	 	i 	3 	) 	r   c                    \        4       p. pV  FB  p\        WA4      pV'       g   K  WR9  g   K   VP                  V4       VP                  V4       KD  	  V# )zHCanonicalize and preserve first-seen order for NYT Cooking recipe links.)setr/   addappend)r1   r   seenurlsr   r   s   &&    r   dedupe_recipe_urlsr9   0   sI    UDD"4233?HHSMKK	 
 Kr   c                 `     ^ RI Hp  ^ RI Hp W3#   \         d   p\	        R4      ThRp?ii ; i)    )TimeoutError)sync_playwrightzYPlaywright is not installed. Run: uv add playwright && uv run playwright install chromiumN)playwright.sync_apir<   r=   ModuleNotFoundError
SystemExit)PlaywrightTimeoutErrorr=   excs      r   _load_playwrightrC   <   s=    N7
 22	  g
	s    -(-c                    V ^8  d   QhRRRR/# )r   
timeout_msintr   Noner   )r   s   "r   r    r    G   s     D D3 D4 Dr   c                    V P                  \        R VR7       \        \        4      ;_uu_ 4        V P	                  RVR7       RRR4       R#   + '       g   i     R# ; i)domcontentloaded
wait_untiltimeoutnetworkidlerL   N)gotoRECIPE_BOX_URLr   	Exceptionwait_for_load_state)pagerE   s   &&r   _wait_for_recipe_boxrT   G   sD    IIn);ZIP 
)		  
 C 
			s   AA	c               $    V ^8  d   QhRRRRRR/# )r   rE   rF   poll_msr   boolr   )r   s   "r   r    r    O   s!      C #  r   c                   \        R4       ^ pW18  d   \        \        4      ;_uu_ 4        V P                  R4      P	                  4       ^ 8  d   \        R4        RRR4       R#  RRR4       V P                  V4       W2,          pVR,          ^ 8X  g   K  \        \        4      ;_uu_ 4        \        W4       RRR4       K  R#   + '       g   i     Lh; i  + '       g   i     K  ; i)a3  Poll the Recipe Box until saved recipes render (i.e. the user is logged in).

Returns True once at least one recipe link appears, or False on timeout. This
replaces a blocking ``input()`` so the flow works in non-interactive runners
(the visible browser stays open; the user just logs in and we detect it).
u   A browser window has opened to your NYT Cooking Recipe Box.
If it shows a login screen, log in there now — no need to touch the terminal.
Waiting for your saved recipes to appear...a[href*="/recipes/"]u6   Recipe Box detected — collecting your saved recipes.NTi N  F)printr   rQ   locatorcountwait_for_timeoutrT   )rS   rE   rV   waiteds   &&& r   _wait_until_logged_inr_   O   s     
	6
 F

i  ||2399;a?NO ! ? ! 	g&E>Q)$$$T6 %$ !  %$$s   1C/CC	C+	c                   V ^8  d   QhRR/# )r   r   r2   r   )r   s   "r   r    r    j   s     % %y %r   c                X    V P                  R 4      P                  R4      p\        V4      # )rY   zB(els) => els.map((el) => el.href || el.getAttribute('href') || ''))r[   evaluate_allr9   )rS   r1   s   & r   _extract_page_recipe_urlsrc   j   s,    LL/0==LE e$$r   Ti0u  i c               0    V ^8  d   QhRRRRRRRRRRR	R
/# )r   profile_dirr   	max_pagesrF   pause_for_loginrW   rE   login_wait_msr   r2   r   )r   s   "r   r    r    q   sL     D DDD D 	D
 D Dr   c                   \        4       w  rV. p\        4       pV! 4       ;_uu_ 4       p	V	P                  P                  \	        V 4      RRRRR/R7      p
V
P
                  '       d   V
P
                  ^ ,          MV
P                  4       p \        W4       V'       dD   \        W4      '       g(   \        R4       . V
P                  4        uuRRR4       # \        W4       ^p^ p V^8X  d   \        M
\         R	V 2pVP                  VR
VR7       \        V4      ;_uu_ 4        VP                  RVR7       RRR4       VP                  R4       \!        V4      p^ pV F7  pVV9  g   K  VP#                  V4       VP%                  V4       V^,          pK9  	  \        RV R\'        V4       RV R24       V'       d   W8  d   M2V^ 8X  d   V^,          pM^ pV^8  d   MV^,          p\)        R4       EK  V
P                  4         RRR4       V#   + '       g   i     L; i  T
P                  4        i ; i  + '       g   i     T# ; i)zDCollect saved recipe URLs from the logged-in NYT Cooking Recipe Box.Fwidthi  heighti  )user_data_dirheadlessviewportzTimed out waiting for the Recipe Box to load (no recipes detected). If you weren't logged in, re-run and log in when the window opens.Nz?page=rI   rJ   rM   rN   zPage z: found z links, z newg      ?)rC   r4   chromiumlaunch_persistent_contextr   pagesnew_pagerT   r_   rZ   closerP   rO   r   rR   r]   rc   r5   r6   r*   r   )re   rf   rg   rE   rh   r=   playwright_timeout_errorall_urlsr7   pwcontextrS   page_numstagnant_pagestarget	page_urls	new_countr   s   &&&&&             r   collect_recipe_box_urlsr}   q   s    1A0B-OHUD			b++77k*tXt4 8 

 $+===w}}Qg6F6F6H/	 2,TAA,
 L MMOm 
	" %T6HN  1} #*+6(< 
 		&-?	T677,,]J,O 8%%d+5d;		$C$ ,!Q		 % hZxI/?x	{RVWX!6>"a'N%&N!Q&AdMMOm 
p O9 874 MMOm 
	p OsP   A!H=
0H(:H=AH($H	8/H(,BH(:H=H% H((H::H==I	c               $    V ^8  d   QhRRRRRR/# )r   r8   r2   dry_runrW   r   list[ImportResult]r   )r   s   "r   r    r       s"      i $ ;M r   c                   . pV  F  pV'       d   VP                  \        VR R7      4       K)   \        P                  ! V4      pT'       d    TP                  \        TRTR7      4       Kg  TP                  \        TRRR7      4       K  	  V#   \         d2   pTP                  \        TR\        T4      R7      4        Rp?K  Rp?ii ; i)zdry-run)r   r   failed)r   r   r   Nsaved-or-existingzCould not extract recipe)r6   r   r$   import_web_reciperQ   r   )r8   r   resultsr   r   rB   s   &&    r   import_urlsr      s    "$GNN<C	BC	,,S1D NN<C8KRVWXNNX<VW  N  	NN<Cs3xPQ	s   BC&CCc               (    V ^8  d   QhRRRRRRRR/# )	r   r(   r   r8   r2   r   r   r   rG   r   )r   s   "r   r    r       s)     : :t :9 :7I :d :r   c                    V P                   P                  R R R7       RTRV Uu. uF  q3P                  NK  	  up/pV P                  \        P
                  ! V^R7      R,           4       R# u upi )T)parentsexist_okr8   r   )indent
N)parentmkdir__dict__
write_textjsondumps)r(   r8   r   rpayloads   &&&  r   write_reportr      s^    KKdT201JJ0G 	OODJJwq1D89 1s   A-
c                    V ^8  d   QhRRRR/# )r   r(   r   r   r2   r   )r   s   "r   r    r       s     	# 	# 	#) 	#r   c                    V P                  4       P                  4        Uu. uF*  qP                  4       '       g   K  VP                  4       NK,  	  pp\        V4      # u upi )ao  Load + canonicalize recipe URLs from a newline-delimited file.

This is the bot-detection-proof collection path: NYT's PerimeterX blocks the
automated browser, but the user's real browser is never blocked, so they
collect their Recipe Box URLs there (see the console snippet in the runbook)
and we import each through the working ``recipes.import_web_recipe`` fetch.
)	read_text
splitlinesstripr9   )r(   lnraws   &  r   read_urls_filer      sI     !% 0 ; ; =
L =":288: =C
Lc"" Ms
   AAc                   V ^8  d   QhRR/# )r   r   zargparse.ArgumentParserr   )r   s   "r   r    r       s      - r   c                    \         P                  ! \        R 7      p V P                  R\        RRR7       V P                  R\        \
        R7       V P                  R\        ^ RR7       V P                  R	R
R7       V P                  RR
R7       V P                  RR
R7       V P                  R\        \	        R4      R7       V # ))descriptionz--urls-fileNzgImport from a newline-delimited URL file instead of scraping the Recipe Box (avoids NYT bot detection).)typedefaulthelpz--profile-dir)r   r   z--max-pagesz 0 means continue until exhaustedz--no-login-pause
store_true)actionz	--dry-runz--collect-onlyz--reportz!nyt-recipe-box-import-report.json)argparseArgumentParser__doc__add_argumentr   DEFAULT_PROFILE_DIRrF   )parsers    r   build_parserr      s    $$9F
1	   d<OP
C1S   *<@
L9
(>
89  
 Mr   c                    V ^8  d   QhRRRR/# )r   argvzlist[str] | Noner   rF   r   )r   s   "r   r    r       s       3 r   c                ~   \        4       P                  V 4      pVP                  '       d;   \        VP                  4      p\	        R \        V4       RVP                   24       MI\        VP                  VP                  VP                  '       * R7      p\	        R\        V4       R24       . pVP                  '       gM   \        W!P                  R7      p\        R V 4       4      p\        R V 4       4      p\	        RV R	V R
24       \        VP                  W#4       \	        RVP                   24       ^ # )zLoaded z unique recipe URLs from )re   rf   rg   z
Collected z unique recipe URLs)r   c              3  J   "   T F  qP                   R 8X  g   K  ^x  K  	  R# 5i)r   Nr   .0r   s   & r   	<genexpr>main.<locals>.<genexpr>	  s     Jw!((6I*IAAw   #
#c              3  J   "   T F  qP                   R 8X  g   K  ^x  K  	  R# 5i)r   Nr   r   s   & r   r   r   
  s     @188x+?QQr   zImport complete: z saved/existing, z failedzReport: )r   
parse_args	urls_filer   rZ   r*   r}   re   rf   no_login_pausecollect_onlyr   r   sumr   report)r   argsr8   r   savedr   s   &     r   mainr      s   >$$T*D~~~dnn-D	{";DNN;KLM&((nn $ 3 33

 	
3t9+%89:"$GdLL9JwJJ@@@!%(9&IJd,	HT[[M
"#r   __main__)i  )F)N)&r   
__future__r   r   r   sys
contextlibr   dataclassesr   pathlibr   timer   urllib.parser   r   r	   r$   rP   COOKING_BASEhomer   r   r/   r9   rC   rT   r_   rc   r}   r   r   r   r   r   r   exitr   r   r   <module>r      s    #   
  !   6 6 =,iik$==     5A  :F 	3D6% ,  DN(:	#02 zHHTV r   