+
    -jO                       R t ^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
t
^ RIt^ RIHt ^ RIHtHtHt ^ RIHtHt ]P(                  P+                  RR4      t]P(                  P+                  R4      R	8H  t]P(                  P+                  R
4      t]'       g2   ]P2                  ! ^04      t]! R]R,           R2]P6                  R7       ]P9                  R4      t]! ]P(                  P+                  RR4      4      t]! ]P(                  P+                  RR4      4      t ]P(                  P+                  RR4      PC                  R4       U u0 uF9  p V PE                  4       '       g   K  V PE                  4       PG                  4       kK;  	  up t$R R lt%R R lt&R R lt'R R lt(R R  lt)R! R" lt*R# R$ lt+R% R& lt,R' R( lt-R) R* lt. ! R+ R,]/4      t0]! ]P(                  P+                  R-R.4      4      t1]! ]P(                  P+                  R/R4      4      t2R0 R1 lt3R2 R3 lt4R4 R5 lt5R6 R7 lt6R8RR9R/R: R; llt7R< R= lt8R> R? lt9RLR@ RA llt:RB RC lt;RD RE lt<RF RG lt=RH RI lt>RJ RK lt?R# u up i )Mu  
auth_module.py — Backend de autenticação magic-link para o Content Studio.

Stack stdlib-only (sqlite3, secrets, hashlib, hmac, base64) — sem dependências externas.

Tabelas (SQLite):
  users(id, email UNIQUE, name, role, beta_access, created_at, updated_at)
  magic_links(id, email, token UNIQUE, expires_at, used_at, created_at)
  sessions(id, user_id, token UNIQUE, expires_at, revoked_at, created_at, user_agent, ip)

Variáveis de ambiente:
  AUTH_DB_PATH      → caminho do SQLite (default: ./auth.db)
  BETA_EMAILS       → whitelist separada por vírgula (ex: a@b.com,c@d.com)
  SECRET_KEY        → chave HMAC pro JWT (gera randômico se ausente, com warning)
  DEV_MODE          → "1" libera retorno do dev_token no response do request-link
  SMTP_HOST         → se setado, send_magic_link_email usa SMTP
  SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM
  MAGIC_LINK_TTL_MIN → minutos pra magic link expirar (default 15)
  SESSION_TTL_DAYS   → dias pra sessão expirar (default 30)
)annotationsN)MIMEText)datetime	timedeltatimezone)OptionalTupleAUTH_DB_PATHzauth.dbDEV_MODE1
SECRET_KEYuO   ⚠️  SECRET_KEY ausente — gerei uma temporária (NÃO use em produção): :N   Nu   …fileutf-8MAGIC_LINK_TTL_MIN15SESSION_TTL_DAYS30BETA_EMAILS ,c                   V ^8  d   QhRR/# )   returnr    )formats   "#/opt/apps/studio-api/auth_module.py__annotate__r   <   s     & &h &    c                 J    \         P                  ! \        P                  4      # N)r   nowr   utcr   r   r   _nowr$   <   s    <<%%r   c                   V ^8  d   QhRR/# )r   r   strr   )r   s   "r   r   r   @   s      # r   c                 2    \        4       P                  4       # r!   )r$   	isoformatr   r   r   _now_isor)   @   s    6r   c                    V ^8  d   QhRRRR/# )r   sr&   r   r   r   )r   s   "r   r   r   D   s     % %# %( %r   c                .    \         P                  ! V 4      # r!   )r   fromisoformat)r+   s   &r   
_parse_isor.   D   s    !!!$$r   c                    V ^8  d   QhRRRR/# )r   bbytesr   r&   r   )r   s   "r   r   r   K   s     D De D Dr   c                j    \         P                  ! V 4      P                  R 4      P                  R4      # )   =ascii)base64urlsafe_b64encoderstripdecode)r0   s   &r   _b64url_encoder9   K   s)    ##A&--d3::7CCr   c                    V ^8  d   QhRRRR/# )r   r+   r&   r   r1   r   )r   s   "r   r   r   O   s     - -c -e -r   c                n    R \        V 4      ) ^,          ,          p\        P                  ! W,           4      # )=)lenr5   urlsafe_b64decode)r+   pads   & r   _b64url_decoder@   O   s)    
#a&1
C##AG,,r   c                    V ^8  d   QhRRRR/# )r   payloaddictr   r&   r   )r   s   "r   r   r   T   s     , , , ,r   c                   R RRR/p\        \        P                  ! VRR7      P                  R4      4      p\        \        P                  ! V RR7      P                  R4      4      pV RV 2P                  R4      p\        P
                  ! \        V\        P                  4      P                  4       pV RV R\        V4       2# )	algHS256typJWT)
separatorsr   .r4   )r   :)
r9   jsondumpsencodehmacnewr   hashlibsha256digest)rB   headerhpsigning_inputsigs   &     r   
jwt_encoderY   T   s    WeU+Ftzz&Z@GGPQAtzz'jAHHQRAc1#J%%g.M
((:}gnn
=
D
D
FCS!AnS)*++r   c                    V ^8  d   QhRRRR/# )r   tokenr&   r   Optional[dict]r   )r   s   "r   r   r   ]   s      c n r   c                F    V P                  R 4      w  rpT R T 2P                  R4      p\        P                  ! \
        T\        P                  4      P                  4       p \        T4      p\        P                  ! YV4      '       g   R#  \        P                  ! \        T4      4      pTP                  R4      pTe)   T\        \!        4       P#                  4       4      8  d   R# T#   \         d     R# i ; i  \         d     R# i ; i  \         d     R# i ; i)rJ   Nr4   exp)split
ValueErrorrN   rO   rP   r   rQ   rR   rS   r@   	Exceptioncompare_digestrL   loadsgetintr$   	timestamp)	r[   rU   rV   r+   rW   expectedprovidedrB   r^   s	   &        r   
jwt_decoderi   ]   s    ++c"a c1#J%%g.Mxx
M7>>BIIKH!!$ x22**^A./ ++e
C
3TV%5%5%7!88N#      s5   C- %C? D -C<;C<?DDD D c                   V ^8  d   QhRR/# )r   r   sqlite3.Connectionr   )r   s   "r   r   r   v   s      $ r   c                     \         P                  ! \        4      p \         P                  V n        V P                  R 4       V # )zPRAGMA foreign_keys = ON)sqlite3connectr	   Rowrow_factoryexecuteconns    r   _connectrt   v   s.    ??<(D{{DLL+,Kr   c                   V ^8  d   QhRR/# )r   r   Noner   )r   s   "r   r   r   }   s     &
 &
 &
r   c                     \        4       ;_uu_ 4       p V P                  R4       RRR4       R#   + '       g   i     R# ; i)uB   Cria as tabelas se ainda não existirem. Chame uma vez no startup.a  
            CREATE TABLE IF NOT EXISTS users (
              id TEXT PRIMARY KEY,
              email TEXT UNIQUE NOT NULL,
              name TEXT,
              role TEXT,
              beta_access INTEGER NOT NULL DEFAULT 1,
              created_at TEXT NOT NULL,
              updated_at TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS magic_links (
              id TEXT PRIMARY KEY,
              email TEXT NOT NULL,
              token TEXT UNIQUE NOT NULL,
              expires_at TEXT NOT NULL,
              used_at TEXT,
              created_at TEXT NOT NULL
            );
            CREATE INDEX IF NOT EXISTS idx_magic_links_token ON magic_links(token);

            CREATE TABLE IF NOT EXISTS sessions (
              id TEXT PRIMARY KEY,
              user_id TEXT NOT NULL,
              token TEXT UNIQUE NOT NULL,
              expires_at TEXT NOT NULL,
              revoked_at TEXT,
              created_at TEXT NOT NULL,
              user_agent TEXT,
              ip TEXT,
              FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
            );
            CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token);
            N)rt   executescriptrr   s    r   init_dbry   }   s*    	t!#	
 
s	   /A 	c                    V ^8  d   QhRRRR/# )r   emailr&   r   boolr   )r   s   "r   r   r      s     0 0 0 0r   c                h    \         '       g   R# V P                  4       P                  4       \         9   # )z/Verifica se o email tem acesso ao beta privado.F)r   striplower)r{   s   &r   is_email_whitelistedr      s$    ;;;= K//r   c                  2   a  ] tR t^tRtR V 3R lltRtV ;t# )RateLimitExceededzDDisparada quando o email passou do limite de magic links por janela.c                   V ^8  d   QhRR/# )r   retry_after_secondsre   r   )r   s   "r   r   RateLimitExceeded.__annotate__   s     b bC br   c                	:   < Wn         \        SV `	  R V R24       R# )z-Muitos links solicitados. Tente novamente em zs.N)r   super__init__)selfr   	__class__s   &&r   r   RateLimitExceeded.__init__   s#    #6 HI\H]]_`ar   )r   )__name__
__module____qualname____firstlineno____doc__r   __static_attributes____classcell__)r   s   @r   r   r      s    Nb br   r   MAGIC_LINK_MAX_PER_HOUR5MAGIC_LINK_MIN_INTERVAL_SECc               $    V ^8  d   QhRRRRRR/# )r   rs   rk   
email_normr&   r   rv   r   )r   s   "r   r   r      s'     P P. PC PD Pr   c                   \        4       pV\        ^R7      ,
          P                  4       pV P                  RW34      P	                  4       pVR,          pV\
        8  d   V P                  RW34      P	                  4       pV'       dX   \        VR,          4      \        ^R7      ,           p\        ^\        Wr,
          P                  4       4      4      p\        V4      hVR,          '       dU   \        VR,          4      p	W),
          P                  4       p
V
\        8  d    \        \        \        V
,
          4      4      hR# R# )u   Lança RateLimitExceeded se o email pediu links demais recentemente.
Política: máx 5 por hora + intervalo mínimo de 30s entre pedidos.)hourszaSELECT COUNT(*) as c, MAX(created_at) as last FROM magic_links WHERE email = ? AND created_at > ?czeSELECT created_at FROM magic_links WHERE email = ? AND created_at > ? ORDER BY created_at ASC LIMIT 1
created_atlastN)r$   r   r(   rq   fetchoner   r.   maxre   total_secondsr   r   )rs   r   r"   hour_agorowcountoldestopens_atretrylast_dtelapseds   &&         r   _check_rate_limitr      s    &Cia((335H ,,k	 hj 
 HE''s"
 (* 	 !&"67)!:LLH3==?@AE#E** 6{{S[)=//100#C(Cg(M$NOO 1 r   c                    V ^8  d   QhRRRR/# r   r{   r&   r   rC   r   )r   s   "r   r   r      s      S T r   c                   V P                  4       P                  4       p\        P                  ! ^4      p\	        4       pV\        \        R7      ,           p\        P                  ! ^4      p\        4       ;_uu_ 4       p\        Wa4       VP                  RWQW$P                  4       VP                  4       34       VP                  4        RRR4       RVRVRVRVP                  4       /#   + '       g   i     L(; i)u   Cria magic link, persiste no DB e retorna dados. Não envia email aqui.

Aplica rate limit: máx 5/hora + intervalo mínimo 30s entre pedidos
(configurável via MAGIC_LINK_MAX_PER_HOUR e MAGIC_LINK_MIN_INTERVAL_SEC).
Lança RateLimitExceeded se exceder.
)minuteszYINSERT INTO magic_links (id, email, token, expires_at, created_at) VALUES (?, ?, ?, ?, ?)Nidr{   r[   
expires_at)r~   r   secretstoken_urlsafer$   r   r   rt   r   rq   r(   commit)r{   r   r[   r"   expireslink_idrs   s   &      r   create_magic_linkr      s     $$&J!!"%E
&CI&899G##A&G	t$+g%):):)<cmmoN	
 	 
 	gg'')	  
s   >AC++C;	c                    V ^8  d   QhRRRR/# )r   r[   r&   r   Optional[str]r   )r   s   "r   r   r      s      S ] r   c                   \        4       p\        4       ;_uu_ 4       pVP                  RV 34      P                  4       pV'       g    RRR4       R# VR,          '       d    RRR4       R# \	        VR,          4      V8  d    RRR4       R# VP                  RVP                  4       VR,          34       VP                  4        VR,          uuRRR4       #   + '       g   i     R# ; i)u   Valida token de magic link. Retorna email se válido, None caso contrário.
Marca o link como `used_at` na sucesso (uso único).zFSELECT id, email, expires_at, used_at FROM magic_links WHERE token = ?Nused_atr   z/UPDATE magic_links SET used_at = ? WHERE id = ?r   r{   )r$   rt   rq   r   r.   r(   r   )r[   r"   rs   r   s   &   r   verify_magic_linkr      s     &C	tllTH
 (* 	  
 y>> 
 c,'(3. 
 	=]]_c$i(	
 	7|! 
s$   +CC!C,CACC+	c                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r     s     " "c "d "r   c           	        V P                  4       P                  4       p\        4       pR\        P                  ! ^4       2p\        4       ;_uu_ 4       pVP                  RW1W"34       VP                  4        VP                  RV34      P                  4       pRRR4       \        X4      #   + '       g   i     L; i)uE   Busca o user pelo email; se não existir, cria. Retorna dict do user.user_z
            INSERT OR IGNORE INTO users (id, email, beta_access, created_at, updated_at)
            VALUES (?, ?, 1, ?, ?)
            z\SELECT id, email, name, role, beta_access, created_at, updated_at FROM users WHERE email = ?N)
r~   r   r)   r   r   rt   rq   r   r   _user_row_to_dict)r{   r   now_isouser_idrs   r   s   &     r   get_or_create_userr     s    $$&JjGg++A./0G	t '3	
 	lljM
 (* 	 
 S!! 
s   AB--B=	namerolec               (    V ^8  d   QhRRRRRRRR/# )r   r   r&   r   r   r   r   r\   r   )r   s   "r   r   r   %  s)     # # #} #= #\j #r   c               P   . . rCVe1   VP                  R4       VP                  VP                  4       4       Ve1   VP                  R4       VP                  VP                  4       4       V'       g   \        V 4      # VP                  R4       VP                  \        4       4       VP                  V 4       \	        4       ;_uu_ 4       pVP                  RRP                  V4       R2V4       VP                  4        RRR4       \        V 4      #   + '       g   i     L; i)zAAtualiza nome e/ou role do user. Retorna user atualizado ou None.Nzname = ?zrole = ?zupdated_at = ?zUPDATE users SET z, z WHERE id = ?)appendr~   get_user_by_idr)   rt   rq   joinr   )r   r   r   setsargsrs   s   &$$   r   update_userr   %  s    R$JDJJL!JDJJL!g&&KK !KK
KK	t(4(9GN 
 '"" 
s   6DD%	c                    V ^8  d   QhRRRR/# )r   r   r&   r   r\   r   )r   s   "r   r   r   9  s     3 3C 3N 3r   c                    \        4       ;_uu_ 4       pVP                  R V 34      P                  4       pRRR4       X'       d   \        V4      # R#   + '       g   i     L%; i)zYSELECT id, email, name, role, beta_access, created_at, updated_at FROM users WHERE id = ?N)rt   rq   r   r   )r   rs   r   s   &  r   r   r   9  sO    	tllgJ
 (* 	 

 &)S!2d2 
s   "AA"	c                    V ^8  d   QhRRRR/# )r   r   zsqlite3.Rowr   rC   r   )r   s   "r   r   r   B  s     	 	; 	4 	r   c                    R V R ,          RV R,          RV R,          RV R,          R\        V R,          4      RV R,          RV R,          /# )r   r{   r   r   beta_accessr   
updated_at)r|   )r   s   &r   r   r   B  sS    c$iWFFtC./c,'c,' r   c               (    V ^8  d   QhRRRRRRRR/# )r   r   r&   
user_agentipr   r   )r   s   "r   r   r   P  s(      C S 3  r   c                    R\         P                  ! ^4       2p\        4       pV\        \        R7      ,           pRVRV R\        VP                  4       4      R\        VP                  4       4      /p\        V4      p\        4       ;_uu_ 4       pVP                  RW0WuP                  4       VP                  4       VR,          VR	,          34       VP                  4        R
R
R
4       V#   + '       g   i     T# ; i)u>   Cria sessão no DB e retorna o JWT pra ser setado como cookie.sess_)dayssiduidiatr^   z
            INSERT INTO sessions (id, user_id, token, expires_at, created_at, user_agent, ip)
            VALUES (?, ?, ?, ?, ?, ?, ?)
            :N   N:N@   NN)r   r   r$   r   r   re   rf   rY   rt   rq   r(   r   )	r   r   r   
session_idr"   r   jwt_payload	jwt_tokenrs   s	   &&&      r   create_sessionr   P  s    ..q123J
&CI#344G 	zws3==?#s7$$&'	K ;'I	t )->->-@#--/S]^bSceghkelm	
 	 
  
 s   AC,,C=	c                    V ^8  d   QhRRRR/# )r   r   r   r   r\   r   )r   s   "r   r   r   i  s     # #m # #r   c                   V '       g   R# \        V 4      pV'       g   R# VP                  R4      pVP                  R4      pV'       d	   V'       g   R# \        4       p\        4       ;_uu_ 4       pVP	                  RW 34      P                  4       pV'       g    RRR4       R# VR,          '       d    RRR4       R# \        VR,          4      V8  d    RRR4       R#  RRR4       \        V4      #   + '       g   i     L; i)u[   Valida JWT + checa session no DB (não revogada, não expirada). Retorna user dict ou None.Nr   r   zFSELECT revoked_at, expires_at FROM sessions WHERE id = ? AND token = ?
revoked_atr   )ri   rd   r$   rt   rq   r   r.   r   )r   rB   r   r   r"   rs   r   s   &      r   verify_sessionr   i  s    #GU#Jkk% GW
&C	tllT#
 (* 	  
 | 
 c,'(3. 
 / 
 '"" 
s   0+C5%C54C5?C55D	c                    V ^8  d   QhRRRR/# )r   r   r   r   r|   r   )r   s   "r   r   r     s        m    r   c                N   V '       g   R# \        V 4      pV'       g   R# VP                  R4      pV'       g   R# \        4       ;_uu_ 4       pVP                  R\	        4       V34      pVP                  4        VP                  ^ 8  uuRRR4       #   + '       g   i     R# ; i)u2   Revoga sessão. Retorna True se algo foi revogado.Fr   zFUPDATE sessions SET revoked_at = ? WHERE id = ? AND revoked_at IS NULLN)ri   rd   rt   rq   r)   r   rowcount)r   rB   r   rs   curs   &    r   revoke_sessionr     ss    #GU#J	tllTZ$
 	||a 
s   ;BB$	c               0    V ^8  d   QhRRRRRRRRRRRR	/# )
r   r{   r&   channeltargetsuccessr|   
message_idr   rv   r   )r   s   "r   r   r     sL     !V !V!V!V !V 	!V
 !V 
!Vr   c                f    \        4       ;_uu_ 4       pVP                  R4       TP                  RYY#'       d   ^M^ V\        4       34       VP                  4        RRR4       R#   + '       g   i     R# ; i  \         d*   p\        RT 2\        P                  R7        Rp?R# Rp?ii ; i)zAAudit trail: registra todo envio de magic link (sucesso ou erro).a  
                CREATE TABLE IF NOT EXISTS magic_links_sent (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    email TEXT NOT NULL,
                    channel TEXT NOT NULL,         -- 'whatsapp' | 'smtp' | 'mock'
                    target TEXT NOT NULL,          -- phone ou email pra onde foi
                    success INTEGER NOT NULL,
                    message_id TEXT,
                    created_at TEXT NOT NULL
                )
                z
                INSERT INTO magic_links_sent (email, channel, target, success, message_id, created_at)
                VALUES (?, ?, ?, ?, ?, ?)
                Nz/[AUDIT WARN] Falha ao gravar magic_links_sent: r   )rt   rq   r)   r   ra   printsysstderr)r{   r   r   r   r   rs   es   &&&&&  r   _log_magic_link_sentr     s    VZZ4LL
 LL g1j(*U KKM+ ZZZ,  V?sC#**UUVs5   A< A
A(A< (A9	3A< 9A< <B0B++B0c               0    V ^8  d   QhRRRRRRRRRRRR	/# )
r   r{   r&   linkr   r   r   r|   r   rv   r   )r   s   "r   r   r     s8     > > >C ># >s >UY >^b >r   c                F    ^ RI pVP                  4       '       g   R# VP                  WW#VR7      w  rgV'       d   \        RV 24       R# \        RV 2\        P
                  R7       R#   \         d*   p\        RT 2\        P
                  R7        Rp?R# Rp?ii ; i)u   
Cria conversation no Chatwoot inbox Dalton-Pessoal com info do magic link.
Não bloqueia caso falhe — apenas warning. Chamado em paralelo com envio principal.
N)r   r   r   z[CHATWOOT] Audit registrado: z[CHATWOOT] Audit falhou: r   z[CHATWOOT] Erro audit: )chatwoot_senderis_configuredaudit_magic_linkr   r   r   ra   )	r{   r   r   r   r   r   okinfor   s	   &&&&&    r   _chatwoot_audit_asyncr     s    

>,,.."33Eip3q1$89-dV43::F >'s+#**==>s"   A, ,A, A, ,B 7BB c               (    V ^8  d   QhRRRRRRRR/# )r   r{   r&   r[   base_urlr   rv   r   )r   s   "r   r   r     s.     J J JS JC JD Jr   c           	     l   VP                  R4       RV 2p ^ RIpVP                  4       '       d   VP                  V\        4      pVP
                  '       d   VP                  MRpV'       g   \        R4      hVP                  WeV R7      w  rx\        V RWgV4       \        WRWg4       V'       d   \        RV R	V  R
V 24       R# \        RV R2\        P                  R7       \        P                   P#                  R4      p
T
'       g<   \        RT  24       \        RT 24       \        RT 24       \        T RT RR4       R# \%        \        P                   P#                  RR4      4      p\        P                   P#                  RR4      p\        P                   P#                  RR4      p\        P                   P#                  RT;'       g    R4      pR\         RT R2p\'        TRR 4      pR!TR"&   TTR#&   T TR$&    \(        P*                  ! Y4      ;_uu_ 4       pTP-                  4        TP/                  4        T'       d   TP1                  Y4       TP3                  Y.TP5                  4       4       RRR4       \        R%T  24       \        T R&T RR'4       R#   \         d+   p	\        RT	 R2\        P                  R7        Rp	?	ELRp	?	ii ; i  + '       g   i     Lg; i  \         d7   p	\        R(T	 2\        P                  R7       \        T R&T R)R*T	 24       h Rp	?	ii ; i)+u  
Envia o magic link na ordem de preferência:
  1. WhatsApp via Evolution API (se EVOLUTION_API_KEY estiver setado)
  2. SMTP (se SMTP_HOST estiver setado)
  3. Mock no stdout

Em modo BETA (default), TODOS os envios WhatsApp vão pro Caue (+5511947452497).
Audit trail em tabela `magic_links_sent`.
Sprint F: também cria conversation no Chatwoot inbox Dalton-Pessoal (audit visual).
/z/?token=Nr   u2   Sem phone alvo fora do modo beta — fallback SMTP)real_recipientwhatsappz#[WHATSAPP] Magic link enviado para z (real: u   ) · id=z[WHATSAPP] Falha: u    — tentando SMTP fallbackr   z[WHATSAPP] Erro: 	SMTP_HOSTz[MOCK EMAIL] Para: z[MOCK EMAIL] Link: z[MOCK EMAIL] Token: mockTzmock-no-channel	SMTP_PORT587	SMTP_USERSMTP_PASSWORD	SMTP_FROMznoreply@studio.localu=   Olá,

Use o link abaixo pra entrar no Six Hype (válido por z min):

u:   

Se você não solicitou este acesso, ignore este email.
plainr   u   Seu link de acesso · Six HypeSubjectFromToz[SMTP] Magic link enviado para smtpzsmtp-okz[SMTP] Falha: Fzerror: )r7   whatsapp_senderr   build_magic_link_messager   	BETA_MODEBETA_WHATSAPP_TARGETRuntimeErrorsend_whatsappr   r   r   r   r   ra   osenvironrd   re   r   smtplibSMTPehlostarttlsloginsendmail	as_string)r{   r[   r   r   r  msg_bodytarget_phoner   r   r   	smtp_host	smtp_port	smtp_usersmtp_password	smtp_frombodymsgr+   s   &&&               r   send_magic_link_emailr'    s    ooc"#8E73DS((**&??FXYH DSC\C\C\???bdL"#WXX&44\\a4bHB 
LdK!%z<L;L>RWQXX`ae`fgh*4&0KLSVS]S]^
 

{+I#E7+,#D6*+$UG,-UFE49JKBJJNN;67I

{B/IJJNN?B7M

{I,O,O9OPIAAS@TT^& D	E 	 4'
*C5C	NCKCI\\)//1FFHJJL	1JJy'3==?; 0 	/w78UFE4CK  S!!$?@szzRRS< 0/  qc"4UFE5GA3-Hs`   J' 'J' J' 0AJ' 	J' K2 %AK %K2 'K2KKK/	*K2 2L3=1L..L3)r   r   )@r   
__future__r   r5   rQ   rO   rL   r  r   rm   r  r   email.mime.textr   r   r   r   typingr   r   r  rd   r	   r
   _secret_envr   r   r   rN   r   re   r   r   r_   r~   r   r   r$   r)   r.   r9   r@   rY   ri   rt   ry   r   ra   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r'  )r   s   0r   <module>r,     s$  * #     	    
 $ 2 2 " zz~~ni8::>>*%,jjnn\*''+K	[\ghj\k[llo
pwz  xB  xB  C(
(<dCD rzz~~&8$?@  jjnn]B/55c::wwy AGGIOO:&%D-
,2&
V0b	 b bjjnn-FLM !"**..1NPT"UV P@62"*#t #SW #(3	2#4 *!VH>$Jws   I:2"I: