
    ?i<                        U d 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 ej                  j                  d      Zedk(  rdZdZn,er ee      Zedz  Zn ej&                         d	z  d
z  Zedz  Z eej                  j                  d e ej&                         dz  dz                    Zed   Zed   ZdZeed<   dZeed<   dZeed<   dZeed<   dZeed<   dZeed<   dZeed<    ed       G d  d!             Z d"ed#eeef   fd$Z!d%ed#e
eeef      fd&Z"dEd%ed'e#d#e$fd(Z%d)ed#e
e   fd*Z&efd"ed#eeef   fd+Z'd#e(e
e   ef   fd,Z)d-eeef   d#e fd.Z*d#eeef   fd/Z+d#e$fd0Z,d1eeef   d#e$fd2Z-d1eeef   d#e
e   fd3Z.d1eeef   d#efd4Z/d1eeef   d#e$fd5Z0d1eeef   d#e
e   fd6Z1d1eeef   d#efd7Z2dFd8ed9ed:e$d#e(ee
e   f   fd;Z3d1eeef   d#e
e   fd<Z4d#e$fd=Z5d#e$fd>Z6d#e$fd?Z7d1eeef   d#e$fd@Z8d1eeef   d#efdAZ9d1eeef   d#e$fdBZ:d1eeef   d#efdCZ;e8Z<d1eeef   d#eeef   fdDZ=y)Gz8Environment and API key management for last30days skill.    N)	dataclass)Path)OptionalDictAnyLiteralLAST30DAYS_CONFIG_DIR z.envz.config
last30daysCODEX_AUTH_FILEz.codexz	auth.json)api_keycodexnone)okmissingexpiredmissing_account_idr   AUTH_SOURCE_API_KEYr   AUTH_SOURCE_CODEXr   AUTH_SOURCE_NONEr   AUTH_STATUS_OKr   AUTH_STATUS_MISSINGr   AUTH_STATUS_EXPIREDr   AUTH_STATUS_MISSING_ACCOUNT_IDT)frozenc                   L    e Zd ZU ee   ed<   eed<   eed<   ee   ed<   eed<   y)
OpenAIAuthtokensourcestatus
account_idcodex_auth_fileN)__name__
__module____qualname__r   str__annotations__
AuthSource
AuthStatus     N/home/ubuntu/.openclaw/workspace/skills/last30days-official/scripts/lib/env.pyr   r   )   s(    C=r+   r   pathreturnc                    i }| j                         s|S t        | d      5 }|D ]  }|j                         }|r|j                  d      r'd|v s,|j	                  d      \  }}}|j                         }|j                         }|r|d   dv r|d   |d   k(  r|dd }|s}|s|||<    	 ddd       |S # 1 sw Y   |S xY w)	z'Load environment variables from a file.r#=r   )"'   N)existsopenstrip
startswith	partition)r-   envflinekey_values          r,   load_env_filerB   2   s    
C;;=
	dC %A 	%D::<D4??3/d{ $s 3QiikU1X3b	U1X8M!!BKE5$CH	%% J% Js   .B6AB6!B6$B66C r   c                    	 | j                  d      }t        |      dk  ry|d   }dt        |       dz  z  }t        j                  ||z         }t	        j
                  |j                  d            S # t        $ r Y yw xY w)z(Decode JWT payload without verification..   Nr6   r2      zutf-8)splitlenbase64urlsafe_b64decodejsonloadsdecode	Exception)r   partspayload_b64paddecodeds        r,   _decode_jwt_payloadrS   I   s    	C u:>Ahc+&&*+**;+<=zz'..122 s   A5 AA5 5	B Bleeway_secondsc                 z    t        |       }|sy|j                  d      }|sy|t        j                         |z   k  S )zCheck if JWT token is expired.Fexp)rS   gettime)r   rT   payloadrV   s       r,   _token_expiredrZ   W   s<    !%(G
++e
C499;/00r+   access_tokenc                     t        |       }|sy|j                  di       }t        |t              r|j                  d      S y)z*Extract chatgpt_account_id from JWT token.Nzhttps://api.openai.com/authchatgpt_account_id)rS   rW   
isinstancedict)r[   rY   
auth_claims      r,   extract_chatgpt_account_idra   b   s@    !,/G:B?J*d#~~233r+   c                     | j                         si S 	 t        | d      5 }t        j                  |      cddd       S # 1 sw Y   yxY w# t        $ r i cY S w xY w)zLoad Codex auth JSON.r0   N)r7   r8   rK   loadrN   )r-   r=   s     r,   load_codex_authrd   m   sS    ;;=	$_ 	 99Q<	  	  	  	s+   A ?	A AA A AAc                     t               } d}t        | t              rI| j                  d      xs i }t        |t              r|j                  d      }|s| j                  d      }|sdt        fS t        |      rdt        fS |t        fS )zGet Codex access token from auth.json.

    Returns:
        (token, status) where status is 'ok', 'missing', or 'expired'
    Ntokensr[   )rd   r^   r_   rW   r   rZ   r   r   )authr   rf   s      r,   get_codex_access_tokenrh   x   s     DE$(#)rfd#JJ~.EHH^,E(((e(((.  r+   file_envc           	         t         j                  j                  d      xs | j                  d      }|r%t        |t        t
        dt        t                    S t               \  }}|rWt        |      }|r%t        |t        t
        |t        t                    S t        dt        t        dt        t                    S t        dt        |dt        t                    S )z0Resolve OpenAI auth from API key or Codex login.OPENAI_API_KEYN)r   r   r    r!   r"   )osenvironrW   r   r   r   r&   r   rh   ra   r   r   r   )ri   r   codex_tokencodex_statusr!   s        r,   get_openai_authrp      s    jjnn-.P(,,?O2PG&!0
 	
 !7 8K/<
!(%% #O 4  $10
 	
 O, r+   c                  F   t         rt        t               ni } t        |       }|j                  |j                  |j
                  |j                  |j                  d}g d}|D ];  \  }}t        j                  j                  |      xs | j                  ||      ||<   = |S )zBLoad configuration from ~/.config/last30days/.env and environment.)rk   OPENAI_AUTH_SOURCEOPENAI_AUTH_STATUSOPENAI_CHATGPT_ACCOUNT_IDr   ))XAI_API_KEYN)OPENROUTER_API_KEYN)PARALLEL_API_KEYN)BRAVE_API_KEYN)OPENAI_MODEL_POLICYauto)OPENAI_MODEL_PINN)XAI_MODEL_POLICYlatest)XAI_MODEL_PINN)SCRAPECREATORS_API_KEYN)APIFY_API_TOKENN)
AUTH_TOKENN)CT0N)CONFIG_FILErB   rp   r   r   r    r!   r"   rl   rm   rW   )ri   openai_authconfigkeysr?   defaults         r,   
get_configr      s     .9}[)bH!(+K &++)00)00%0%;%;&66FD  HWjjnnS)GX\\#w-GsH Mr+   c                  *    t         j                         S )z#Check if configuration file exists.)r   r7   r*   r+   r,   config_existsr      s    r+   r   c                     t        | j                  d            }t        | j                  d            xr | j                  d      t        k(  }|xs |S )zjCheck if Reddit search is available.

    Reddit can use either ScrapeCreators (preferred) or OpenAI.
    r   rk   rs   )boolrW   r   )r   has_sc
has_openais      r,   is_reddit_availabler      sL    
 &**567Ffjj!123j

CW8X\j8jJZr+   c                 |    | j                  d      ry| j                  d      r| j                  d      t        k(  ryy)zDetermine which Reddit backend to use.

    Priority: ScrapeCreators (cheaper, faster) > OpenAI (legacy)

    Returns: 'scrapecreators', 'openai', or None
    r   scrapecreatorsrk   rs   openaiN)rW   r   r   s    r,   get_reddit_sourcer      s8     zz*+zz"#

3G(HN(Zr+   c                     t        |       }t        | j                  d            }t        |       }|r|r|rdS dS |r|rdS dS |r|rdS dS |ryy)	zDetermine which sources are available based on API keys.

    Returns: 'all', 'both', 'reddit', 'reddit-web', 'x', 'x-web', 'web', or 'none'
    ru   allboth
reddit-webredditx-webxweb)r   r   rW   has_web_search_keys)r   
has_reddithas_xaihas_webs       r,   get_available_sourcesr      sf    
 %V,J6::m,-G!&)Ggu+V+	&|4H4	!w*s*	r+   c                     t        | j                  d      xs$ | j                  d      xs | j                  d            S )z0Check if any web search API keys are configured.rv   rw   rx   r   rW   r   s    r,   r   r     s8    

/0qFJJ?Q4RqV\V`V`apVqrrr+   c                 p    | j                  d      ry| j                  d      ry| j                  d      ryy)zDetermine the best available web search backend.

    Priority: Parallel AI > Brave > OpenRouter/Sonar Pro

    Returns: 'parallel', 'brave', 'openrouter', or None
    rw   parallelrx   braverv   
openrouterNrW   r   s    r,   get_web_search_sourcer     s6     zz$%zz/"zz&'r+   c                     t        |       }t        | j                  d            }t        |       }ddlm} |j                         xr |j                         }|xs |}|r|r|ry|r|ry|ry|ryy)	zDetermine which sources are missing (accounting for Bird and ScrapeCreators).

    Returns: 'all', 'both', 'reddit', 'x', 'web', or 'none'
    ru   r6   bird_xr   r   r   r   r   )r   r   rW   r   r
   r   is_bird_installedis_bird_authenticated)r   r   r   r   r   has_birdhas_xs          r,   get_missing_keysr   !  sw    
 %V,J6::m,-G!&)G '')Lf.J.J.LHxEe			r+   	requested	availableinclude_webc                    |dk(  r| dk(  ry| dk(  rydS |dk(  r| dk(  ry| dk(  ryy| dk(  r|r|dk(  ry|d	k(  ry
|dk(  ry|dfS | dk(  ry| dk(  r|dvr|d	k(  rdnd}dd| dfS |ryy| d	k(  r
|dk(  ry|ry
y| dk(  r
|d	k(  ry|ryy| dfS )a7  Validate requested sources against available keys.

    Args:
        requested: 'auto', 'reddit', 'x', 'both', or 'web'
        available: Result from get_available_sources()
        include_web: If True, add WebSearch to available sources

    Returns:
        Tuple of (effective_sources, error_message)
    r   rz   )r   zWNo API keys configured. The assistant can still search the web if it has a search tool.r   )r   N)r   zgOnly web search keys configured. Add OPENAI_API_KEY (or run codex login) for Reddit, XAI_API_KEY for X.r   )r   Nr   )r   Nr   )r   NN)r   xAIOpenAIzRequested both sources but z: key is missing. Use --sources=auto to use available keys.)r   N)r   z/Requested Reddit but only xAI key is available.)r   N)r   z-Requested X but only OpenAI key is available.)r   N)r   zKNo API keys configured. Add keys to ~/.config/last30days/.env for Reddit/X.r*   )r   r   r   r   s       r,   validate_sourcesr   <  s    Fs%hh E% DFF""h&)c!$$EFI%(H4e(G8	A{|||HL%C J d?r+   c                 z    ddl m} |j                         r|j                         }|ry| j	                  d      ryy)u9  Determine the best available X/Twitter source.

    Priority: Bird (free) → xAI (paid API)

    Args:
        config: Configuration dict from get_config()

    Returns:
        'bird' if Bird is installed and authenticated,
        'xai' if XAI_API_KEY is configured,
        None if no X source available.
    r6   r   birdru   xaiN)r
   r   r   r   rW   )r   r   usernames      r,   get_x_sourcer     s=      !//1 zz- r+   c                  .    ddl m}  | j                         S )z0Check if yt-dlp is installed for YouTube search.r6   
youtube_yt)r
   r   is_ytdlp_installedr   s    r,   is_ytdlp_availabler     s    ((**r+   c                       y)zrCheck if Hacker News source is available.

    Always returns True - HN uses free Algolia API, no key needed.
    Tr*   r*   r+   r,   is_hackernews_availabler         
 r+   c                       y)zjCheck if Polymarket source is available.

    Always returns True - Gamma API is free, no key needed.
    Tr*   r*   r+   r,   is_polymarket_availabler     r   r+   c                 \    t        | j                  d      xs | j                  d            S )zCheck if TikTok source is available (ScrapeCreators or legacy Apify).

    Returns True if SCRAPECREATORS_API_KEY or APIFY_API_TOKEN is set.
    r   r   r   r   s    r,   is_tiktok_availabler     s(    
 

34U

CT8UVVr+   c                 R    | j                  d      xs | j                  d      xs dS )zBGet TikTok API token, preferring ScrapeCreators over legacy Apify.r   r   r
   r   r   s    r,   get_tiktok_tokenr     s'    ::./V6::>O3PVTVVr+   c                 6    t        | j                  d            S )zCheck if Instagram source is available (ScrapeCreators).

    Returns True if SCRAPECREATORS_API_KEY is set.
    Instagram uses the same key as TikTok.
    r   r   r   s    r,   is_instagram_availabler     s     

3455r+   c                 ,    | j                  d      xs dS )z<Get Instagram API token (same ScrapeCreators key as TikTok).r   r
   r   r   s    r,   get_instagram_tokenr     s    ::./525r+   c                     ddl m} |j                         }t        | j	                  d            }|d   rd}n|rd}nd}||d   |d   |d	   ||d
   dS )zGet detailed X source status for UI decisions.

    Returns:
        Dict with keys: source, bird_installed, bird_authenticated,
        bird_username, xai_available, can_install_bird
    r6   r   ru   authenticatedr   r   N	installedr   can_install)r   bird_installedbird_authenticatedbird_usernamexai_availablecan_install_bird)r
   r   get_bird_statusr   rW   )r   r   bird_statusr   r   s        r,   get_x_source_statusr     sv     ((*KM23M ?#	 %k2)/:$Z0&'6 r+   )<   )F)>__doc__rI   rK   rl   rX   dataclassesr   pathlibr   typingr   r   r   r   rm   rW   _config_override
CONFIG_DIRr   homer&   r   r(   r)   r   r'   r   r   r   r   r   r   r   rB   rS   intr   rZ   ra   rd   tuplerh   rp   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   is_apify_availabler   r*   r+   r,   <module>r      s   >   	  !  / /
 ::>>"9: rJK&'Jv%Ky(<7Jv%Krzz~~&7YTYY[8=SVa=a9bcd/0
EF
"+ Z + ' : '% * %!
 !"+ Z +"+ Z +-A 
 A $   c3h .s xS#X'? 1# 1s 1D 1S Xc]  "1 $ T#s(^ !hsmS&8 9 !*%d38n % %P"DcN "J t  
 S#X  4  d38n # $sCx. S *sS#X s4 s
$sCx. Xc]  T#s(^  6A A A$ ASXY\^fgj^kYkSl AHc3h HSM :+D +  WS#X W4 WWT#s(^ W W
64S> 6d 66S#X 63 6 ) S#X 4S> r+   