
    ?ik?                        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ZddlmZ ddl	m	Z	 ddl
mZmZmZmZmZ  ee      j"                  dz  dz  dz  Zd	d
ddZi Zeeef   ed<   dee   dee   fdZdeeef   fdZdefdZdedefdZdefdZdee   fdZdefdZdeeef   fdZdeeef   fdZ dede!de!deeef   fdZ"	 d)ded ed!ed"edeeef   f
d#Z#	 d*d$ee   dee   d ed%e!deeeef      f
d&Z$d'eeef   deeeef      fd(Z%y)+zBird X search client - vendored Twitter GraphQL search for /last30days v2.1.

Uses a vendored subset of @steipete/bird v0.8.0 (MIT License) to search X
via Twitter's GraphQL API. No external `bird` CLI binary needed - just Node.js 22+.
    N)Path)datetime)AnyDictListOptionalTuplevendorzbird-searchzbird-search.mjs      <   )quickdefaultdeep_credentials
auth_tokenct0c                 2    | r	| t         d<   |r
|t         d<   yy)zIInject AUTH_TOKEN/CT0 from .env config so Node subprocesses can use them.
AUTH_TOKENCT0N)r   )r   r   s     Q/home/ubuntu/.openclaw/workspace/skills/last30days-official/scripts/lib/bird_x.pyset_credentialsr      s!    %/\"
!U     returnc                  l    t         j                  j                         } | j                  t               | S )zCBuild env dict for Node subprocesses, merging injected credentials.)osenvironcopyupdater   )envs    r   _subprocess_envr!   '   s#    
**//
CJJ|Jr   msgc                     t         j                  j                  d|  d       t         j                  j                          y)zLog to stderr.z[Bird] 
N)sysstderrwriteflush)r"   s    r   _logr)   .   s-    JJwse2&'JJr   topicc                    | j                         j                         }g d}|D ]4  }|j                  |dz         s|t        |      d j                         } n g d}|D ]5  }|j	                  d|z         s|dt        |        j                         } n h d}|j                         }|D cg c]	  }||vs| }	}dj                  |	dd       xs | j                         j                         S c c}w )u   Extract core subject from verbose query for X search.

    X search is literal keyword AND matching — all words must appear.
    Aggressively strip question/meta/research words to keep only the
    core product/concept name (2-3 words max).
    )zwhat are the bestzwhat is the bestzwhat are the latestzwhat are people saying aboutzwhat do people think aboutzhow do i usez
how to usezhow tozwhat arezwhat isztips forzbest practices for N)zbest practicesz	use caseszprompt techniqueszprompting techniqueszprompting tips>E   aaninisofonortovsandareforhotnewthetopusewasbestgoodnewssaidtipstooluseswerewithaboutgreatguideskillthinktoolsusingviraladvicekillerlatelylatestpeoplepluginpromptreviewsayingskillstricksr   versusawesomehottestmethodspluginspopularpromptsreviewsupdatesexamplesfeaturestrendingtutorialusecases	practices	prompting	trendiest
approaches
comparison
strategies
techniquesrecommendations   )lowerstrip
startswithlenendswithsplitjoin)
r*   textprefixespsuffixess_noisewordswresults
             r   _extract_core_subjectr   4   s     ;;= DH  ??1s7#A=&&(DH  ==q!#a&>'')DF& JJLE2A!6/a2F288F2AJ85;;=#6#6#88 3s   2	C6<C6c                  Z    t         j                         syt        j                  d      duS )zCheck if vendored Bird search module is available.

    Returns:
        True if bird-search.mjs exists and Node.js 22+ is in PATH.
    FnodeN)_BIRD_SEARCH_MJSexistsshutilwhich r   r   is_bird_installedr   m   s'     ""$<<t++r   c                     t               sy	 t        j                  dt        t              dgdddt                     } | j                  dk(  rF| j                  j                         r,| j                  j                         j                  d      d   S y# t        j                  t        t        j                  f$ r Y yw xY w)	zCheck if X credentials are available (env vars or browser cookies).

    Returns:
        Auth source string if authenticated, None otherwise.
    Nr   z--whoamiT   )capture_outputry   timeoutr    r   r$   )r   
subprocessrunstrr   r!   
returncodestdoutrs   rw   TimeoutExpiredFileNotFoundErrorSubprocessError)r   s    r   is_bird_authenticatedr   x   s     S)*J7!
 !fmm&9&9&;==&&(..t4Q77%%'8*:T:TU s   BB (C ?C c                  0    t        j                  d      duS )zCheck if npm is available (kept for API compatibility).

    Returns:
        True if 'npm' command is available in PATH, False otherwise.
    npmN)r   r   r   r   r   check_npm_availabler      s     <<d**r   c                  Z    t               ryt        j                  d      syddt         fS )zxNo-op - Bird search is vendored in v2.1, no installation needed.

    Returns:
        Tuple of (success, message).
    )TzFBird search is bundled with /last30days v2.1 - no installation needed.r   )Fz<Node.js 22+ is required for X search. Install Node.js first.Fz&Vendored bird-search.mjs not found at )r   r   r   r   r   r   r   install_birdr      s1     ]<<T:;K:LMMMr   c                  D    t               } | r
t               nd}| |du|ddS )zGet comprehensive Bird search status.

    Returns:
        Dict with keys: installed, authenticated, username, can_install
    NT)	installedauthenticatedusernamecan_install)r   r   )r   auth_sources     r   get_bird_statusr      s6     "#I-6')DK $D0	 r   querycountr   c           	      "   dt        t              | dt        |      dg}t        t        d      rt        j                  nd}	 t        j                  |t
        j                  t
        j                  d|t                     }	 dd	l	m
}m}  ||j                         	 |j                  |
      \  }}		 	 ddl	m}  ||j                         |j4                  dk7  r|	r|	j7                         nd}
|
g dS |r|j7                         nd}|sdg iS t9        j:                  |      S # t        $ r Y w xY w# t
        j                  $ r 	 t        j                   t        j"                  |j                        t$        j&                         n*# t(        t*        t,        f$ r |j/                          Y nw xY w|j1                  d
       d| dg dcY 	 ddl	m}  ||j                         S # t        t2        f$ r Y S w xY ww xY w# t        t2        f$ r Y Gw xY w# 	 ddl	m}  ||j                         w # t        t2        f$ r Y w w xY wxY w# t8        j<                  $ r}d| g dcY d}~S d}~wt2        $ r}t        |      g dcY d}~S d}~ww xY w)a  Run a search using the vendored bird-search.mjs module.

    Args:
        query: Full search query string (including since: filter)
        count: Number of results to request
        timeout: Timeout in seconds

    Returns:
        Raw Bird JSON response or error dict.
    r   --count--jsonsetsidNT)r   r&   ry   
preexec_fnr    r   )register_child_pidunregister_child_pidr      zSearch timed out after r}   )erroritems)r   zBird search failed r   zInvalid JSON response: )r   r   hasattrr   r   r   PopenPIPEr!   
last30daysr   r   pidImportErrorcommunicater   killpggetpgidsignalSIGTERMProcessLookupErrorPermissionErrorOSErrorkillwait	Exceptionr   rs   jsonloadsJSONDecodeError)r   r   r   cmdpreexecprocr   r   r   r&   r   outputes                r   _run_bird_searchr      sF    	$%3u:	C #2x0biidG/.????!
	Ktxx(	!--g->NFF;$TXX. ??a&,FLLN2FE"R00#)rR= zz&!!9  		
 (( 	P		"**TXX.?&A 		IIaI 6wiqABOO;$TXX.+ 	P  + ;$TXX.+   E21#6DD .Q"--.s  ?I
 D# D2 4H 'I
 4I
 I
 #	D/,I
 .D//I
 2G>AFG>$F/,G>.F//G>H G((G:7I
 9G::I
 =G>>H HI
 HI
 IH21I2IIIII
 
JI*$J*J6J	J	J	from_dateto_datedepthc                 N   t         j                  |t         d         }|dk(  rdn|dk(  rdnd}t        |       }| d| }t        d|        t	        |||      }t        |      }	|j                         }
|	sSt        |
      dkD  rEd	j                  |
d
d       }t        d| d| d       | d| }t	        |||      }t        |      }	|	sS|
rQh d}|
D cg c]	  }||vs| }}|r7t        |t              }t        d| d| d       | d| }t	        |||      }|S c c}w )aZ  Search X using Bird CLI with automatic retry on 0 results.

    Args:
        topic: Search topic
        from_date: Start date (YYYY-MM-DD)
        to_date: End date (YYYY-MM-DD) - unused but kept for API compatibility
        depth: Research depth - "quick", "default", or "deep"

    Returns:
        Raw Bird JSON response or error dict.
    r   r   r   -   r    since:zSearching:    r,   Nz0 results for 'z', retrying with ''>   r9   r:   r<   r?   rD   rK   rM   rO   rS   rU   rY   r]   r_   r`   rf   rk   )keyz"', retrying with strongest token ')
DEPTH_CONFIGgetr   r)   r   parse_bird_responserw   ru   rx   max)r*   r   r   r   r   r   
core_topicr   responser   
core_wordsshorter
low_signalr   
candidates	strongests                   r   search_xr      sf   " UL$;<EW$b0B"G 'u-Jl')-E;ug	ug6H  )E !!#JS_q(((:bq>*zl*<WIQGH)79+.#E5':#H- Z


 ",CAq
/BaC
CJC0I?:,.PQZP[[\]^ k4E'ug>HO Ds   	D"!D"handles	count_perc                 ~   g }|rt        |      nd}| D ]3  }|j                  d      }|rd| d| d| }nd| d| }dt        t              |dt        |      dg}t	        t
        d	      rt
        j                  nd}		 t        j                  |t        j                  t        j                  d
|	      }
	 |
j                  d      \  }}|
j.                  dk7  r$t-        d| d|xs dj1                                 |xs dj1                         }|st3        j4                  |      }t7        |      }|j9                  |       6 |S # t        j                  $ r 	 t        j                  t        j                  |
j                        t        j                          n*# t"        t$        t&        f$ r |
j)                          Y nw xY w|
j+                  d       t-        d|        Y w xY w# t2        j:                  $ r t-        d|        Y t<        $ r}t-        d| d|        Y d}~#d}~ww xY w)a  Search specific X handles for topic-related content.

    Runs targeted Bird searches using `from:handle topic` syntax.
    Used in Phase 2 supplemental search after entity extraction.

    Args:
        handles: List of X handles to search (without @)
        topic: Search topic (core subject), or None for unfiltered search
        from_date: Start date (YYYY-MM-DD)
        count_per: Results to request per handle

    Returns:
        List of raw item dicts (same format as parse_bird_response output).
    N@zfrom:r,   r   r   r   r   r   T)r   r&   ry   r   r   r   r   zHandle search timed out for @r   zHandle search failed for @z: r   z%Invalid JSON from handle search for @zHandle search error for @)r   lstripr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r)   r   rs   r   r   r   extendr   r   )r   r*   r   r   	all_itemsr   handler   r   r   r   r   r&   r   r   r   r   s                    r   search_handlesr   6  s$   ( I16&u-DJ 3<s#F81ZL	{CEF879+6E C()s9~	
  'r84"))$#	<##!!"D	!%!1!1"!1!= !#1&V\r<P<P<R;STUl))+Fzz&)H'1EU#]3<j 5 ,,  IIbjj2FNNC*OWE  IIK 		!	$4VH=>* ## 	C8AB 	<,VHBqc:;;	<ss    6G47E2G4?G41G4G1!AF#"G1#$G
G1	G

#G1-G40G11G44!H<H< H77H<r   c                    g }d| v r| d   rt        d| d           |S t        | t              r| n!| j                  d| j                  dg             }t        |t              s|S t	        |      D ]  \  }}t        |t
              s|j                  d      xs |j                  dd      }|si|j                  d      rX|j                  d	i       xs |j                  d
i       }|j                  d      xs |j                  dd      }|rd| d|d    }|sd}|j                  d      xs |j                  dd      }	|	rd	 t        |	      dkD  r.|	d   dk(  r&t        j                  |	j                  dd            }
nt        j                  |	d      }
|
j                  d      }|j                  d	i       xs |j                  d
i       }|j                  d      xs& |j                  dd      xs |j                  dd      }|j                  d      xs$ |j                  d      xs |j                  d      |j                  d      xs |j                  d      |j                  d      xs |j                  d      |j                  d       xs |j                  d!      d"}|D ]  }||   		 t        ||         ||<    d#|d$z    t        |j                  d%|j                  d&d                  j!                         dd' ||j#                  d(      |t%        d) |j'                         D              r|nddd*d+}|j)                  |        |S # t        t        f$ r Y w xY w# t        t        f$ r d||<   Y w xY w),zParse Bird response to match xai_x output format.

    Args:
        response: Raw Bird JSON response

    Returns:
        List of normalized item dicts matching xai_x.parse_x_response() format.
    r   zBird error: r   tweetspermanent_urlurlr   idauthoruserr   screen_namezhttps://x.com/z/status/N	createdAt
created_at
   TZz+00:00z%a %b %d %H:%M:%S %z %Yz%Y-%m-%dauthor_handle	likeCount
like_countfavorite_countretweetCountretweet_count
replyCountreply_count
quoteCountquote_count)likesrepostsrepliesquotesX   ry   	full_texti  r   c              3   $   K   | ]  }|d u 
 y w)Nr   ).0vs     r   	<genexpr>z&parse_bird_response.<locals>.<genexpr>  s     +WaATM+Ws   gffffff?)r   ry   r   r   date
engagementwhy_relevant	relevance)r)   
isinstancelistr   	enumeratedictru   r   fromisoformatreplacestrptimestrftime
ValueError	TypeErrorintr   rs   r   anyvaluesappend)r   r   	raw_itemsitweetr   r   r   r  r   dtr   r  r   items                  r   r   r     sv    E (x0|HW-./0 'x6HLLRZR^R^_gikRl<mIi&i( ?5%& ii(@EIIeR,@uyyYYx,E		&"0EF **Z0QFJJ}b4QK&{m8E$K=I YY{+Juyyr/J

 z?R'JrNc,A!//
0B0B30QRB "**:7PQB{{:.
 8R(AEIIfb,A

:.q&**]B2OqSXS\S\]lnpSq YY{+euyy/Fe%))TdJeyy0NEIIo4Nyy.J%))M2Jii-I=1I	

  	+C#*+&)*S/&:JsO	+ acU)		&%))K*DEFLLNtPST*11#6(++W:CTCTCV+W(W*]a	
 	T?B LI 	* & #I. +&*JsO+s%   A#M5M0M-,M-0NN)r   )r   )&__doc__r   r   r   r   r   r%   pathlibr   r   typingr   r   r   r   r	   __file__parentr   r   r   r   __annotations__r   r!   r)   r   boolr   r   r   r   r   r  r   r   r   r   r   r   r   <module>r*     s    	    
   3 3 >((83mCFWW    "d38n !" "HSM "c3h c 69 69 69r,4 ,x} 0+T +
NeD#I& 
Nc3h "D.C D. D.c D.d38n D.V 	555 5 	5
 
#s(^5x 	L#YLC=L L 	L
 
$sCx.L^W$sCx. WT$sCx.5I Wr   