
    ?i                     h   d Z ddlZddlZddlZddlZddlmZmZ ddlm	Z	m
Z
mZmZ ddlmZ dZdZd	Zd
dddZddddZdefdZdedefdZdedefdZdedefdZ	 d&dedededede
ee	f   f
dZde
ee	f   dee
ee	f      fd Zd'd!ed"ede
ee	f   fd#Z	 d&d$ee
ee	f      dedee
ee	f      fd%Zy)(zHacker News search via Algolia API (free, no auth required).

Uses hn.algolia.com/api/v1 for story discovery and comment enrichment.
No API key needed - just HTTP calls via stdlib urllib.
    N)ThreadPoolExecutoras_completed)AnyDictListOptional   )httpz$https://hn.algolia.com/api/v1/searchz,https://hn.algolia.com/api/v1/search_by_datez#https://hn.algolia.com/api/v1/items      <   )quickdefaultdeep      
   msgc                     t         j                  j                         rBt         j                  j                  d|  d       t         j                  j	                          yy)zHLog to stderr (only in TTY mode to avoid cluttering Claude Code output).z[HN] 
N)sysstderrisattywriteflush)r   s    U/home/ubuntu/.openclaw/workspace/skills/last30days-official/scripts/lib/hackernews.py_logr   !   sA    
zz

5R)

     date_strreturnc                    | j                  d      }t        |d         t        |d         t        |d         }}}ddl}ddl}|j                  ||||j                  j
                        }t        |j                               S )z8Convert YYYY-MM-DD to Unix timestamp (start of day UTC).-r   r	      N)tzinfo)splitintcalendardatetimetimezoneutc	timestamp)r   partsyearmonthdayr'   r(   dts           r   _date_to_unixr1   (   sq    NN3E58}c%(mSq]%D			4H4E4E4I4I		JBr||~r   tsc                     ddl }|j                   j                  | |j                  j                        }|j	                  d      S )z%Convert Unix timestamp to YYYY-MM-DD.r   N)tzz%Y-%m-%d)r(   fromtimestampr)   r*   strftime)r2   r(   r0   s      r   _unix_to_dater7   2   s;    				(	(0A0A0E0E	(	FB;;z""r   textc                     ddl }t        j                  |       } |j                  dd|       } |j                  dd|       } | j	                         S )z9Strip HTML tags and decode entities from HN comment text.r   Nz<p>r   z<[^>]+> )rehtmlunescapesubstrip)r8   r;   s     r   _strip_htmlr@   9   sC    ==D66&$%D66*b$'D::<r   topic	from_dateto_datedepthc           	      N   t         j                  |t         d         }t        |      }t        |      dz   }t        d|  d| d| d       | dd| d	| t	        |      d
}ddlm} t         d ||       }		 t        j                  d|	d      }
|
j                  dg       }t        dt        |       d       |
S # t        j                  $ r&}t        d|        g t	        |      dcY d}~S d}~wt        $ r&}t        d|        g t	        |      dcY d}~S d}~ww xY w)a  Search Hacker News via Algolia API.

    Args:
        topic: Search topic
        from_date: Start date (YYYY-MM-DD)
        to_date: End date (YYYY-MM-DD)
        depth: 'quick', 'default', or 'deep'

    Returns:
        Dict with Algolia response (contains 'hits' list).
    r   iQ zSearching for 'z	' (since z, count=)storyzcreated_at_i>z,created_at_i<)querytagsnumericFiltershitsPerPager   )	urlencode?GETr   timeoutzSearch failed: )hitserrorNrQ   zFound z stories)DEPTH_CONFIGgetr1   r   strurllib.parserL   ALGOLIA_SEARCH_URLr
   request	HTTPError	Exceptionlen)rA   rB   rC   rD   countfrom_tsto_tsparamsrL   urlresponseerQ   s                r   search_hackernewsrc   B   s1   " UL$;<EI&G'"U*E?5'9+XeWA	FG )'.H5z	F ' )F"3!4
5C-<<sB7 <<#D6#d)H	%&O >> -qc"#SV,, -qc"#SV,,-s0   :B> >D$C2,D$2D$>DD$D$ra   c                    | j                  dg       }g }t        |      D ]#  \  }}|j                  dd      }|j                  d      xs d}|j                  d      xs d}|j                  d      }d}	|rt        |      }	|j                  d	      xs d}
d
| }t        dd|dz  z
        }t	        dt        j                  |      dz        }t	        d|dz  |z   dz         }|j                  ||j                  dd      |
||j                  dd      |	||dt        |d      d|j                  dd      dd  d	       & |S )zyParse Algolia response into normalized item dicts.

    Returns:
        List of item dicts ready for normalization.
    rQ   objectIDr:   pointsr   num_commentscreated_at_iNr`   z%https://news.ycombinator.com/item?id=g333333?g      ?g{Gz?g?(   gffffff?g?titleauthor)rf   rg   r#   zHN story about rA   r   )		object_idrj   r`   hn_urlrk   date
engagement	relevancewhy_relevant)	rT   	enumerater7   maxminmathlog1pappendround)ra   rQ   itemsihitrl   rf   rg   rh   r   article_urlrm   
rank_scoreengagement_boostrp   s                  r   parse_hackernews_responser   r   si    <<#DED/ !3GGJ+	"'aww~.3!ww~.$\2H ggen*8D cQX./
sDJJv$6$;<Z#-0@@3FG	"WWWb)ggh+  , y!,-cgggw.G.L-MN
 	)!F Lr   rl   max_commentsc                    t          d|  }	 t        j                  d|d      }|j                  d	g       }|D cg c]&  }|j                  d
      r|j                  d      r|( }}|j                  d d       g }g }	|d| D ]  }t        |j                  d
d            }
t        |
      dkD  r|
dd dz   n|
}|j                  |j                  dd      ||j                  d      xs dd       |
j                  d      d   j                  d      d   dd }|s|	j                  |        ||	dS # t        $ r }t	        d|  d|        g g dcY d}~S d}~ww xY wc c}w )zFetch top-level comments for a story from Algolia items endpoint.

    Args:
        object_id: HN story ID
        max_comments: Max comments to return

    Returns:
        Dict with 'comments' list and 'comment_insights' list.
    /rN   r   rO   zFailed to fetch comments for z: )commentscomment_insightsNchildrenr8   rk   c                 ,    | j                  d      xs dS )Nrf   r   rT   )cs    r   <lambda>z&_fetch_item_comments.<locals>.<lambda>   s    QUU8_%9 r   Tkeyreverser:   i,  z...rf   r   )rk   r8   rf   z. r      )ALGOLIA_ITEM_URLr
   rX   rZ   r   rT   sortr@   r[   rw   r%   )rl   r   r`   datarb   r   r   real_commentsr   insightsr8   excerptfirst_sentences                r   _fetch_item_commentsr      s    a	{
+C8||E33
 xx
B'H 55=QUU8_ 	
M  94HHH=L) ,155,-(+D	C$t*u$TeeHb)eeHo*
 	 D)!,2248;DSAOON+, !h??9  8,YKr!=>B778s"   D7 +E#7	E  EE E ry   c           	      (     s S t         j                  |t         d         }t        t        t	                      fdd      }|d| }t        dt	        |       d       t        d	      5 }|D ci c]  }|j                  t         |   d
         |! }}t        |      D ]0  }||   }	 |j                  d      }	|	d    |   d<   |	d    |   d<   2 	 ddd        S c c}w # t        $ r g  |   d<   g  |   d<   Y ^w xY w# 1 sw Y    S xY w)zFetch comments for top N stories by points.

    Args:
        items: Parsed HN items
        depth: Research depth (controls how many to enrich)

    Returns:
        Items with top_comments and comment_insights added.
    r   c                 N    |    j                  di       j                  dd      S )Nro   rf   r   r   )rz   ry   s    r   r   z$enrich_top_stories.<locals>.<lambda>   s#    eAhll<4881E r   Tr   NzEnriching top z stories with commentsr   )max_workersrl   r   rO   r   top_commentsr   )ENRICH_LIMITSrT   sortedranger[   r   r   submitr   r   resultrZ   )
ry   rD   limit	by_points	to_enrichexecutoridxfuturesfuturer   s
   `         r   enrich_top_storiesr      sa    e]9%=>E c%jEI
 &5!I>#i.))?	@A		* 4h !

 	 OO$c
;' 
 
 #7+ 	4F&/C4r2-3J-?c
>*178J1Kc
-.	44& L%
  4-/c
>*13c
-.44& LsB   /D4$C#D.(C(D#D(DDDDD)r   )r   )__doc__r<   ru   r   timeconcurrent.futuresr   r   typingr   r   r   r   r:   r
   rW   ALGOLIA_SEARCH_BY_DATE_URLr   rS   r   rU   r   r&   r1   r7   r@   rc   r   r   r    r   r   <module>r      sn     
  ? , , ; K 8   c C C #c #c #c c  	--- - 	-
 
#s(^-`,S#X ,4S#X;O ,^*@C *@s *@4S> *@^ /S#X// 
$sCx./r   