
    `gjh                   v   U d Z ddlm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	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ZddlmZ ddlmZ ddlmZmZmZmZmZ ddlmZmZ  ej:                  e      ZdZ 	 ddl!Z!d	Z"	 ddl$m%Z% ddl&m'Z'm(Z(m)Z) d	Z*ddl+m,Z,m-Z- ddl.m/Z/ ddl0m1Z1m2Z2m3Z3m4Z4m5Z5m6Z6m7Z7 ddl8m9Z9 ddl:m;Z; ddl<m=Z= dZ>dZ?dZ@dZAdZBdZCdZDdZEdZFdZGdZHdZIdZJd ZKd!ZLdZMd"ZNd#ZOd$ZPd%ZQd&ZRd'ZS	 	 	 	 	 	 	 	 dud(ZTd)ZUd#ZVd"ZWd*ZXi ZYd+eZd,<   dvd-Z[d)Z\d#Z]d"Z^d*Z_d.Z`d)Zad#Zbd#Zcd)Zdd#Ze e
j                  d/      Zg e
j                  d0      Zh e
j                  d1      Zi e
j                  d2      Zjdwd3Zkdxdyd4Zldzd5Zmd{d|d6Znd}d7Zod~d8Zpdd9Zqdd:Zrdd;Zsdd<Ztdd=Zudd>Zvd?d@	 	 	 	 	 	 	 	 	 	 	 ddAZwddBZx G dC dD      Zy G dE dF      ZzddGZ{ddHZ|ddIZ}ddJZ~	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddKZ	 	 	 	 	 	 	 	 	 	 ddLZ	 	 	 	 	 	 	 	 	 	 	 	 ddMZ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddNZ	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddOZ	 	 	 	 	 	 	 	 	 	 	 	 ddPZ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddQZ	 	 	 	 	 	 	 	 ddRZdSdT	 	 	 	 	 	 	 ddUZ eh dV      ZdWeZdX<   ddYZddZZ	 	 	 	 	 	 	 	 	 	 	 	 	 	 dd[Zdd\Zdd]Zdd^Zdd_Zdd`ZddaZddbZddcZdddZddeZ	 d	 	 	 	 	 	 	 ddfZdddgZddhZddiZddjZddkZddlZdmdndo	 	 	 	 	 	 	 ddpZ G dq dre1      Zdds	 	 	 	 	 	 	 	 	 	 	 ddtZy# e#$ r dZ!d
Z"Y w xY w# e#$ r dZ%dZ'dZ(dZ)d
Z*Y w xY w)a{  
Weixin platform adapter.

Connects Hermes Agent to WeChat personal accounts via Tencent's iLink Bot API.

Design notes:
- Long-poll ``getupdates`` drives inbound delivery.
- Every outbound reply must echo the latest ``context_token`` for the peer.
- Media files move through an AES-128-ECB encrypted CDN protocol.
- QR login is exposed as a helper for the gateway setup wizard.
    )annotationsN)datetime)Path)AnyDictListOptionalTuple)quoteurlparsex   TF)default_backend)Cipher
algorithmsmodes)PlatformPlatformConfig)MessageDeduplicator)BasePlatformAdapterMessageEventMessageType
SendResultcache_audio_from_bytescache_document_from_bytescache_image_from_bytes)get_hermes_home)atomic_json_write)
get_secretzhttps://ilinkai.weixin.qq.comz%https://novac2c.cdn.weixin.qq.com/c2cbotz2.2.0i  zilink/bot/getupdateszilink/bot/sendmessagezilink/bot/sendtypingzilink/bot/getconfigzilink/bot/getuploadurlzilink/bot/get_bot_qrcodezilink/bot/get_qrcode_statusi  i:  i'           ii,  c                V    | t         k7  r
|t         k7  ry|xs dj                         dk(  S )zTrue when iLink returns ret=-2 / errcode=-2 with 'unknown error',
    which is a stale-session signal (same as errcode=-14) rather than
    a genuine rate limit.F unknown error)RATE_LIMIT_ERRCODElower)reterrcodeerrmsgs      K/root/.hermes/venv/lib/python3.12/site-packages/gateway/platforms/weixin.py_is_stale_session_retr-   d   s0       W0B%BLb!_44          Dict[str, Any]_LIVE_ADAPTERSc                     	 ddl } ddl}t        sy| j	                  |j                               }t        j                  |      S # t        $ r Y yw xY w)a  Return a TCPConnector with a certifi CA bundle, or None if certifi is unavailable.

    Tencent's iLink server (``ilinkai.weixin.qq.com``) is not verifiable against
    some system CA stores (notably Homebrew's OpenSSL on macOS Apple Silicon).
    When ``certifi`` is installed, use its Mozilla CA bundle to guarantee
    verification. Otherwise fall back to aiohttp's default (which honors
    ``SSL_CERT_FILE`` env var via ``trust_env=True``).
    r   N)cafile)ssl)r5   certifiImportErrorAIOHTTP_AVAILABLEcreate_default_contextwhereaiohttpTCPConnector)r5   r6   ssl_ctxs      r,   _make_ssl_connectorr>   w   sS     (((@GG,,  s   A 	AA   z^(#{1,6})\s+(.+?)\s*$z3^\s*\|?(?:\s*:?-{3,}:?\s*\|)+\s*:?-{3,}:?\s*\|?\s*$z^```([^\n`]*)\s*$z\[([^\]]+)\]\(([^)]+)\)c                     t         xr t        S )z?Return True when runtime dependencies for Weixin are available.)r8   CRYPTO_AVAILABLE r.   r,   check_weixin_requirementsrC      s    1!11r.   c                l    t        | xs d      j                         }|syt        |      |k  r|S |d | S )Nr%   ?)strstriplen)valuekeepraws      r,   _safe_idrL      s<    
ekr

 
 
"C
3x4
u:r.   c                2    t        j                  | dd      S )NF),:)ensure_ascii
separators)jsondumps)payloads    r,   _json_dumpsrU      s    ::gEjIIr.   c                H    |t        |       |z  z
  }| t        |g|z        z   S N)rH   bytes)data
block_sizepad_lens      r,   
_pkcs7_padr\      s,    CI
23G%	G+,,,r.   c                    t        t        j                  |      t        j                         t                     }|j                         }|j                  t        |             |j                         z   S )Nbackend)
r   r   AESr   ECBr   	encryptorupdater\   finalize)	plaintextkeycipherrb   s       r,   _aes128_ecb_encryptrh      sT    JNN3'o>OPF  "IJy12Y5G5G5IIIr.   c                X   t        t        j                  |      t        j                         t                     }|j                         }|j                  |       |j                         z   }|s|S |d   }d|cxk  rdk  r(n |S |j                  t        |g      |z        r|d |  S |S )Nr^   r/      )r   r   r`   r   ra   r   	decryptorrc   rd   endswithrX   )
ciphertextrf   rg   rl   paddedr[   s         r,   _aes128_ecb_decryptrp      s    JNN3'o>OPF  "Ij)I,>,>,@@FRjGGrM %ooeWI.>.HIix  Mr.   c                    | dz   dz   dz  dz  S )Nr/      rk   rB   )sizes    r,   _aes_padded_sizert      s    AX]r!R''r.   c                     t        j                  dt        j                  d            d   } t	        j
                  t        |       j                  d            j                  d      S )Nz>Ir0   r   utf-8ascii)	structunpacksecretstoken_bytesbase64	b64encoderF   encodedecode)rI   s    r,   _random_wechat_uinr      sM    MM$ 3 3A 67:ECJ--g67>>wGGr.   c                     dt         iS )Nchannel_version)CHANNEL_VERSIONrB   r.   r,   
_base_infor      s    //r.   c           	         ddt        t        |j                  d                  t               t        t        t
              d}| rd|  |d<   |S )Nzapplication/jsonilink_bot_tokenrv   )Content-TypeAuthorizationTypezContent-LengthzX-WECHAT-UINiLink-App-IdiLink-App-ClientVersionzBearer Authorization)rF   rH   r~   r   ILINK_APP_IDILINK_APP_CLIENT_VERSION)tokenbodyheaderss      r,   _headersr      sR    *.c$++g"678*,$#&'?#@G %,UG#4 Nr.   c                N    t        |       dz  dz  }|j                  dd       |S )NweixinaccountsT)parentsexist_ok)r   mkdir)hermes_homepaths     r,   _account_dirr      s+    x'*4DJJtdJ+Kr.   c                $    t        |       | dz  S )Nz.jsonr   r   
account_ids     r,   _account_filer      s    $*U';;;r.   r%   )user_idc                   |||t        j                  dt        j                               d}t        | |      }t	        ||       	 |j                  d       y# t        $ r Y yw xY w)z,Persist account credentials for later reuse.z%Y-%m-%dT%H:%M:%SZ)r   base_urlr   saved_ati  N)timestrftimegmtimer   r   chmodOSError)r   r   r   r   r   rT   r   s          r,   save_weixin_accountr      sd     MM"6F	G j1DdG$

5 s   A 	A%$A%c                    t        | |      }|j                         sy	 t        j                  |j	                  d            S # t
        $ r Y yw xY w)z#Load persisted account credentials.Nrv   encoding)r   existsrR   loads	read_text	Exceptionr   r   r   s      r,   load_weixin_accountr     sK    j1D;;=zz$..'.:;; s   $A 	AAc                  H    e Zd ZdZd
dZddZddZddZddZddZ	ddZ
y	)ContextTokenStorez<Disk-backed ``context_token`` cache keyed by account + peer.c                2    t        |      | _        i | _        y rW   )r   _root_cache)selfr   s     r,   __init__zContextTokenStore.__init__  s    !+.
&(r.   c                &    | j                   | dz  S )Nz.context-tokens.json)r   )r   r   s     r,   _pathzContextTokenStore._path  s    zzzl*>???r.   c                    | d| S )NrO   rB   r   r   r   s      r,   _keyzContextTokenStore._key  s    Qwi((r.   c                   | j                  |      }|j                         sy 	 t        j                  |j	                  d            }d}|j                         D ]=  \  }}t        |t              s|s|| j                  | j                  ||      <   |dz  }? |r!t        j                  d|t        |             y y # t
        $ r*}t        j                  dt        |      |       Y d }~y d }~ww xY w)Nrv   r   z3weixin: failed to restore context tokens for %s: %sr   r/   z+weixin: restored %d context token(s) for %s)r   r   rR   r   r   r   loggerwarningrL   items
isinstancerF   r   r   info)r   r   r   rY   excrestoredr   r   s           r,   restorezContextTokenStore.restore  s    zz*%{{}	::dnngn>?D "jjl 	NGU%%%>CDIIj':;A	 KKExQYZdQef   	NNPRZ[eRfhkl	s   %B? ?	C2 C--C2c                X    | j                   j                  | j                  ||            S rW   )r   getr   r   s      r,   r   zContextTokenStore.get+  s     {{tyyW=>>r.   c                d    || j                   | j                  ||      <   | j                  |       y rW   )r   r   _persist)r   r   r   r   s       r,   setzContextTokenStore.set.  s'    6;DIIj'23j!r.   c                R   | d}| j                   j                         D ci c]%  \  }}|j                  |      r|t        |      d  |' }}}	 t	        | j                  |      |       y c c}}w # t        $ r*}t        j                  dt        |      |       Y d }~y d }~ww xY w)NrO   z3weixin: failed to persist context tokens for %s: %s)
r   r   
startswithrH   r   r   r   r   r   rL   )r   r   prefixrf   rI   rT   r   s          r,   r   zContextTokenStore._persist2  s    <q! #kk//1
U~~f% F%
 

	mdjj4g>
  	mNNPRZ[eRfhkll	ms   *A-A3 3	B&< B!!B&N)r   rF   )r   rF   returnr   )r   rF   r   rF   r   rF   )r   rF   r   None)r   rF   r   rF   r   Optional[str])r   rF   r   rF   r   rF   r   r   )__name__
__module____qualname____doc__r   r   r   r   r   r   r   rB   r.   r,   r   r     s+    F)@)g"?"
mr.   r   c                  *    e Zd ZdZdddZddZd	dZy)
TypingTicketCachez3Short-lived typing ticket cache from ``getconfig``.c                     || _         i | _        y rW   )_ttl_secondsr   )r   ttl_secondss     r,   r   zTypingTicketCache.__init__B  s    '46r.   c                    | j                   j                  |      }|sy t        j                         |d   z
  | j                  k\  r| j                   j	                  |d        y |d   S )Nr/   r   )r   r   r   r   pop)r   r   entrys      r,   r   zTypingTicketCache.getF  sU    (99;q!T%6%66KKOOGT*Qxr.   c                J    |t        j                          f| j                  |<   y rW   )r   r   )r   r   tickets      r,   r   zTypingTicketCache.setO  s     &		4Gr.   N)g     @)r   float)r   rF   r   r   )r   rF   r   rF   r   r   )r   r   r   r   r   r   r   rB   r.   r,   r   r   ?  s    =75r.   r   c                D    | j                  d       dt        |d       S )N/z /download?encrypted_query_param=r%   saferstripr   )cdn_base_urlencrypted_query_params     r,   _cdn_download_urlr   S  s*    !!#&''GNcjlHmGnoor.   c                `    | j                  d       dt        |d       dt        |d       S )Nr   z/upload?encrypted_query_param=r%   r   z	&filekey=r   )r   upload_paramfilekeys      r,   _cdn_upload_urlr   W  s>    s#
$ %""'2">!?
E'+,	.r.   c                   t        j                  |       }t        |      dk(  r|S t        |      dk(  r<|j                  dd      }|r't	        d |D              rt
        j                  |      S t        dt        |       d      )	Nrk       rw   ignore)errorsc              3  $   K   | ]  }|d v  
 yw)0123456789abcdefABCDEFNrB   ).0chs     r,   	<genexpr>z!_parse_aes_key.<locals>.<genexpr>e  s     F266Fs   zunexpected aes_key format (z decoded bytes))r|   	b64decoderH   r   allrX   fromhex
ValueError)aes_key_b64decodedtexts      r,   _parse_aes_keyr   _  sy    {+G
7|r
7|r~~gh~7CFFF==&&
23w<.P
QQr.   c                   t        | j                  d      xs | j                  d      xs d      j                         }t        | j                  d      xs d      j                         }t        |      xs# |xr |xr ||k7  xr | j                  d      dk(  }|r(d|xs" |xs t        | j                  d      xs d      fS d	t        | j                  d      xs d      fS )
Nroom_idchat_room_idr%   
to_user_idmsg_typer/   groupfrom_user_iddm)rF   r   rG   bool)messager   r  r  is_groups        r,   _guess_chat_typer  j  s    '++i(MGKK,GM2NTTVGW[[.4"5;;=JG}y!x
!xzZ?W!x\c\g\ghr\swx\xHW:WW[[5P5VTV1WWWW[[06B777r.   c                   K   t        i |dt               i      |j                  d       d d fd}t        j                   |       |dz         d {   S 7 w)N	base_infor   c                 f  K   j                  t                    4 d {   } | j                          d {   }| j                  s!t	        d d| j
                   d|d d        t        j                  |      cd d d       d {    S 7 o7 Y7 	# 1 d {  7  sw Y   y xY ww)NrY   r   ziLink POST  HTTP :    )postr   r   okRuntimeErrorstatusrR   r   )responserK   r   endpointsessionr   urls     r,   _doz_api_post.<locals>._do  s     <<$8M<N 	# 	#RZ 'C;;"[
&@QQSTWX\Y\T]S^#_``::c?		# 	# 	#'	# 	# 	# 	#sU   #B1BB1BBABB1BB1BB1B."B%#B.*B1  timeoutr   r1   )rU   r   r   asynciowait_for)	r  r   r  rT   r   
timeout_msr  r   r  s	   ` ` `  @@r,   	_api_postr#  s  sh      ='=;
=>D__S!"!H:
.C# # !!#%d1BCCCCs   AA*#A($A*c                   K   |j                  d       d t        t        t              dd fd}t	        j
                   |       |dz         d {   S 7 w)Nr   )r   r   c                 P  K   j                        4 d {   } | j                          d {   }| j                  s!t        d d| j                   d|d d        t        j                  |      cd d d       d {    S 7 o7 Y7 	# 1 d {  7  sw Y   y xY ww)N)r   z
iLink GET r  r  r  )r   r   r  r  r  rR   r   )r  rK   r  r   r  r  s     r,   r  z_api_get.<locals>._do  s     ;;sG;4 	# 	# 'C;;"Zz?PPRSVW[X[S\R]#^__::c?		# 	# 	#'	# 	# 	# 	#sU   B&BB&BBAB9B&BB&BB&B#BB#B&r  r  r  )r   r   rF   r   r   r!  )r  r   r  r"  r  r   r  s   ` `  @@r,   _api_getr&    sb      __S!"!H:
.C$#&'?#@G# # !!#%d1BCCCCs   AA%A#A%c                  K   	 t        | |t        d|i||       d {   S 7 # t        j                  $ r	 dg |dcY S w xY ww)Nget_updates_bufr   r  rT   r   r"  r   )r)   msgsr(  )r#  EP_GET_UPDATESr   TimeoutError)r  r   r   sync_bufr"  s        r,   _get_updatesr.    s_     
C#&1!
 
 	
 
  C"BBCs0   A% #% A% AA AAc                  K   |r|j                         st        d      d||t        t        t        d|idgd}|r||d<   t        | |t        d|i|t               d	{   S 7 w)
zSend a text message via iLink sendmessage API.

    Returns the raw API response dict (may contain error codes like
    ``errcode: -14`` for session expiry that the caller can inspect).
    z%_send_message: text must not be emptyr%   r   )type	text_itemr  r  	client_idmessage_typemessage_state	item_listcontext_tokenmsgr)  N)rG   r   MSG_TYPE_BOTMSG_STATE_FINISH	ITEM_TEXTr#  EP_SEND_MESSAGEAPI_TIMEOUT_MS)r  r   r   tor   r7  r3  r	  s           r,   _send_messager?    s      tzz|@AA$)(~FGG #0   !   s   AA' A%!A'c          	     Z   K   t        | |t        |||d|t               d {    y 7 w)N)ilink_user_idtyping_ticketr  r)  )r#  EP_SEND_TYPINGCONFIG_TIMEOUT_MS)r  r   r   r  rB  r  s         r,   _send_typingrE    s9      '*

 $  s   !+)+c               f   K   d|i}|r||d<   t        | |t        ||t               d {   S 7 w)NrA  r7  r)  )r#  EP_GET_CONFIGrD  )r  r   r   r   r7  rT   s         r,   _get_configrH    sH       /8G#0 $   s   (1/1c       	        b   K   t        | |t        ||||||d|	d|t               d {   S 7 w)NT)r   
media_typer  rawsize
rawfilemd5filesizeno_need_thumbaeskeyr)  )r#  EP_GET_UPLOAD_URLr=  )
r  r   r   r  rJ  r   rK  rL  rM  
aeskey_hexs
             r,   _get_upload_urlrR    sM      "$$$ ! 	
 !   s   &/-/c               l    K   d fd}t        j                   |       d       d{   S 7 w)u   Upload encrypted media to the CDN.

    Accepts either a constructed CDN URL (from upload_param) or a direct
    upload_full_url — both use POST with the raw ciphertext as the body.
    c            	       K   j                  ddi      4 d {   } | j                  dk(  rq| j                  j                  d      }|r+| j	                          d {    |cd d d       d {    S | j                          d {   }t        d|d d        | j                          d {   }t        d| j                   d|d d        7 7 x7 h7 Q7 *# 1 d {  7  sw Y   y xY ww)	Nr   application/octet-streamr  r  zx-encrypted-paramz-CDN upload missing x-encrypted-param header: zCDN upload HTTP r  )r  r  r   r   readr   r  )r  encrypted_paramrK   rn   r  
upload_urls      r,   
_do_uploadz&_upload_ciphertext.<locals>._do_upload6  s    <<
nVpEq<r 		R 		Rv~#%"*"2"2"6"67J"K""--/))*		R 		R 		R %MMO+"%RSVW[X[S\R]#^__ 'C!1(//1B"S#YKPQQ		R *			R ,'		R 		R 		Rs   C8CC8A C#"C#C#(C84C5C8:C#C(C#6C!7"C#C8C#C8C#!C##C5)C,*C51C8r   r  Nr   rF   r   r!  )r  rn   rX  rY  s   ``` r,   _upload_ciphertextr\  (  s*     
R !!*,<<<<s   (424      N@)timeout_secondsc               h    K   d fd}t        j                   |       |       d {   S 7 w)Nc                    K   j                        4 d {   } | j                          | j                          d {   cd d d       d {    S 7 =7 7 	# 1 d {  7  sw Y   y xY wwrW   )r   raise_for_statusrV  )r  r  r  s    r,   _do_downloadz%_download_bytes.<locals>._do_downloadL  s`     ;;s# 	) 	)x%%'!(	) 	) 	)(	) 	) 	) 	)sV   A2AA2$AAAA2AA2AA2A/#A&$A/+A2r  )r   rX   r[  )r  r  r^  rb  s   ``  r,   _download_bytesrc  D  s)     ) !!,./JJJJs   '202>   wx.qlogo.cnmmbiz.qpic.cnres.wx.qq.commmbiz.qlogo.cnthirdwx.qlogo.cnilinkai.weixin.qq.comnovac2c.cdn.weixin.qq.comzfrozenset[str]_WEIXIN_CDN_ALLOWLISTc                   	 t        |       }|j                  j                         }|j                  xs d}|dvrt        d|d      |t        vrt        d|d      y# t        $ r}t        d|       |d}~ww xY w)	zDRaise ValueError if *url* does not point at a known WeChat CDN host.r%   zUnparseable media URL: N>   httphttpsz Media URL has disallowed scheme z ; only http/https are permitted.zMedia URL host zG is not in the WeChat CDN allowlist. Refusing to fetch to prevent SSRF.)r   schemer(   hostnamer   r   rk  )r  parsedro  hostr   s        r,   _assert_weixin_cdn_urlrs  `  s    E#$$&$" &&.vj8XY
 	
 ((dX &1 1
 	
 )  E23':;DEs   5A" "	A?+A::A?c                R    | j                  |      xs i j                  d      xs i S )Nmedia)r   )itemrf   s     r,   _media_referencerw  t  s%    HHSMR$$W-33r.   c                  K   |r!t        | t        ||      |       d {   }n/|r"t        |       t        | ||       d {   }nt        d      |rt	        |t        |            }|S 7 M7 +w)N)r  r^  z7media item had neither encrypt_query_param nor full_url)rc  r   rs  r  rp   r   )r  r   r   r   full_urlr^  rK   s          r,   _download_and_decrypt_mediarz  x  sz      #!,0EF+
 

 
x(#G?[[TUU!#~k'BCJ
 \s!   A2A.#A2A0*A20A2c                :    t        j                  |       d   xs dS )Nr   rU  )	mimetypes
guess_type)filenames    r,   _mime_from_filenamer    s    )!,J0JJr.   c                    | j                         }|j                  d      r|dd  }|j                  d      r|d d }|j                  d      D cg c]  }|j                          c}S c c}w )N|r/   rj   )rG   r   rm   split)linerowcells      r,   _split_table_rowr    s]    
**,C
~~c!"g
||C#2h%(YYs^4TDJJL444s   A*c                   | j                         }g }d}d}|D ]  }|j                         }t        j                  |j	                               r| }|j                  |       d}M|r|j                  |       a|j	                         s|dz  }|dk  r|j                  d       d}|j                  |        dj                  |      j	                         S )NFr   r/   r%   

splitlinesr   	_FENCE_REmatchrG   appendjoin)contentlinesresultin_code_block	blank_runraw_liner  s          r,   _normalize_markdown_blocksr    s     EFMI  ??4::<( --MMM$IMM$zz|NIA~b!	d'* 99V""$$r.   c           	        | s| S g }d}| j                         D ]  }|j                         }|j                         }t        j	                  |      r| }|j                  |       M|s:t        |      t        k  s(|r&|j                  d      st        j	                  |      r|j                  |       t        j                  |t        dddd      }|j                  |xs |g        dj                  |      j                         S )z@Wrap long display lines that are hard to copy in WeChat clients.Fr  T)widthbreak_long_wordsbreak_on_hyphensreplace_whitespacedrop_whitespacer  )r  r   rG   r  r  r  rH   WEIXIN_COPY_LINE_WIDTHr   _TABLE_RULE_REtextwrapwrapextendr  )r  wrappedr  r  r  strippedwrapped_liness          r,   $_wrap_copy_friendly_lines_for_weixinr    s    GM&&( 0 ::<??8$ --MNN4  4y22""3'##H-NN4  (""$ 
 	}./70: 99W##%%r.   c                   | sg S g }| j                         }g }d}|D ]  }|j                         }t        j                  |j	                               r{|s2|r0|j                  dj                  |      j	                                g }|j                  |       | }|s0|j                  dj                  |      j	                                g }|r|j                  |       |j	                         s4|r0|j                  dj                  |      j	                                g }
|j                  |        |r.|j                  dj                  |      j	                                |D cg c]  }|s|	 c}S c c}w )NFr  r  )r  blocksr  currentr  r  r  blocks           r,   _split_markdown_blocksr    sB   	F EGM  ??4::<( Wdii06689NN4  --M dii06689NN4 zz|dii06689t-0 dii(..01%/eE///s   /E>7E>c                   g }t        |       D ]J  }t        j                  |j                         d   j	                               r|j                  |       Jg }|j                         D ]  }|j                         }|j	                         s3|r0|j                  dj                  |      j	                                g }Vt        |      xr |j                  d      }|r|j                  |       |r.|j                  dj                  |      j	                                |g} |s|j                  dj                  |      j	                                M |D cg c]  }|s|	 c}S c c}w )aQ  Split formatted content into chat-friendly delivery units.

    Weixin can render Markdown, but chat readability is better when top-level
    line breaks become separate messages. Keep fenced code blocks intact and
    attach indented continuation lines to the previous top-level line so nested
    list items do not get torn apart.
    r   r   	)
r  r  r  r  rG   r  r   r  r  r   )r  unitsr  r  r  r  is_continuationunits           r,    _split_delivery_units_for_weixinr  
  s8    E'0 5??5++-a06689LL((* 	H??$D::<LL7!3!9!9!;< G"7mP0C0CK0POt$TYYw/5578fG	" LL7+1134154 #+TdD+++s    E/(E/c                   | j                         }|syt        |      dkD  ry| j                  d      ry|j                  d      ryt        j	                  |      ryt        j                  d|      ryt        j                  d|      ryy)z?Return True when a line looks like a standalone chat utterance.F0   r  )>-*u   【#r  z^\*\*[^*]+\*\*$z^\d+\.\sT)rG   rH   r   r  r  rer  r  s     r,   "_looks_like_chatty_line_for_weixinr  1  sz    zz|H
8}r{#;<H%	xx"H-	xxX&r.   c                    | j                         }|syt        j                  |      ryt        |      dk  xr |j	                  d      S )z5Return True when a short line behaves like a heading.FT   )rO   u   ：)rG   
_HEADER_REr  rH   rm   r  s     r,   #_looks_like_heading_line_for_weixinr  E  sC    zz|H!x=BB8#4#4\#BBr.   c                    | j                         D cg c]  }|j                         s| }}dt        |      cxk  rdk  sy yt        |d         ryt	        d |D              S c c}w )z<Split only chat-like multiline blocks into separate bubbles.r!      Fr   c              3  2   K   | ]  }t        |        y wrW   )r  )r   r  s     r,   r   z<_should_split_short_chat_block_for_weixin.<locals>.<genexpr>V  s     JD1$7Js   )r  rG   rH   r  r   )r  r  r  s      r,   )_should_split_short_chat_block_for_weixinr  O  sh    #..0AdDJJLTAEAE
a  *584JEJJJ Bs
   A'A'c                D   t        |       |k  r| gS g }d}t        |       D ]i  }|s|n| d| }t        |      |k  r|}|r|j                  |       d}t        |      |k  r|}E|j                  t	        j
                  ||             k |r|j                  |       |S )Nr%   z

)rH   r  r  r  r   truncate_message)r  
max_lengthpackedr  r  	candidates         r,    _pack_markdown_blocks_for_weixinr  Y  s    
7|z!yFG'0 O!(E	eW.E	y>Z'GMM'"Gu:#G)::5*MNO gMr.   c                   | sg S |rzt        |       |k  rd| vr| gS g }t        |       D ]=  }t        |      |k  r|j                  |       #|j                  t	        ||             ? |D cg c]  }|s|	 c}xs | gS t        |       |k  r*t        |       rt        |       D cg c]  }|s|	 c}S | gS t	        | |      xs | gS c c}w c c}w )u  Split content into sequential Weixin messages.

    *compact* (default): Keep everything in a single message whenever it fits
    within the platform limit, even when the author used explicit line breaks.
    Only fall back to block-aware packing when the payload exceeds
    ``max_length``.

    *per_line* (``split_per_line=True``): Legacy behavior — top-level line
    breaks become separate chat messages; oversized units still use
    block-aware packing.

    The active mode is controlled via ``config.yaml`` ->
    ``platforms.weixin.extra.split_multiline_messages`` (``true`` / ``false``)
    or the env var ``WEIXIN_SPLIT_MULTILINE_MESSAGES``.
    r  )rH   r  r  r  r  r  )r  r  split_per_linechunksr  cus          r,   _split_text_for_weixin_deliveryr  p  s    $ 	w<:%$g*=94W= 	ND4yJ&d#MM:4LM		N
 "'aQ'4G94
 7|z! 9A 9AG1QQG	
 	

 ,GZ@MWIM ( Hs   -C
5C
'C/Cc                    | |S t        | t              r| S t        | t        t        f      rt        |       S t	        |       j                         j                         }|s|S |dv ry|dv ry|S )zBCoerce a config value to bool, tolerating strings like ``"true"``.>   1onyestrueT>   0noofffalseF)r   r  intr   rF   rG   r(   )rI   defaultr   s      r,   _coerce_boolr    ss    }%%#u&E{u:##%D))**Nr.   c                `   | D ]Q  }|j                  d      t        k(  st        |j                  d      xs i j                  d      xs d      }|j                  d      xs i }|j                  d      xs i }|j                  d      }|t        t        t
        t        hv r5|j                  d      xs d}|rd| d	nd
}| | j                         c S |rxg }|j                  d      r|j                  t        |d                t        |g      }	|	r|j                  |	       |r'ddj                  |       d	| j                         c S |c S  | D ]R  }|j                  d      t        k(  st        |j                  d      xs i j                  d      xs d      }
|
sP|
c S  y)Nr0  r1  r   r%   ref_msgmessage_itemtitleu   [引用媒体: z]
u   [引用媒体]
u	   [引用: z | 
voice_item)r   r;  rF   
ITEM_IMAGE
ITEM_VIDEO	ITEM_FILE
ITEM_VOICErG   r  _extract_textr  )r6  rv  r   refref_itemref_typer  r   partsref_text
voice_texts              r,   r  r    s    88Fy(-388@FBGD((9%+Cww~.4"H||F+HJ
IzJJ(.B9>?5'5DV $(..00#%777#LLS\!23((4LL*&uzz%'8&9TFCIIKKK'(  "88Fz)dhh|4:??GM2NJ!!	"
 r.   c                T   t        d | D              rt        j                  S t        d | D              rt        j                  S t        d | D              rt        j                  S | rt        j
                  S |j                  d      rt        j                  S t        j                  S )Nc              3  >   K   | ]  }|j                  d         yw)image/Nr   r   ms     r,   r   z+_message_type_from_media.<locals>.<genexpr>       
7a1<<!
7   c              3  >   K   | ]  }|j                  d         yw)video/Nr  r  s     r,   r   z+_message_type_from_media.<locals>.<genexpr>  r  r  c              3  >   K   | ]  }|j                  d         yw)audio/Nr  r  s     r,   r   z+_message_type_from_media.<locals>.<genexpr>  r  r  r   )	anyr   PHOTOVIDEOVOICEDOCUMENTr   COMMANDTEXT)media_typesr   s     r,   _message_type_from_mediar    s    

7;
77   

7;
77   

7;
77   ###s"""r.   c                $    t        |       | dz  S )Nz
.sync.jsonr   r   s     r,   _sync_buf_pathr    s    $*Z'@@@r.   c                    t        | |      }|j                         sy	 t        j                  |j	                  d            j                  dd      S # t        $ r Y yw xY w)Nr%   rv   r   r(  )r  r   rR   r   r   r   r   r   s      r,   _load_sync_bufr    sZ    +z2D;;=zz$..'.:;??@QSUVV s   4A 	A A c                8    t        | |      }t        |d|i       y )Nr(  )r  r   )r   r   r-  r   s       r,   _save_sync_bufr    s    +z2Dd.9:r.   3i  )bot_typer^  c               ,  K   t         st        d      t        j                  dt	                     4 d{   }	 t        |t        t         d| t               d{   }t        |j                  d      xs d	      }t        |j                  d
      xs d	      }|s't        j                  d       	 ddd      d{    y|r|n|}t        d       |rt        |       	 ddl}	|	j!                         }
|
j#                  |       |
j%                  d       |
j'                  d       t)        j*                         |z   }t        }d}t)        j*                         |k  r	 t        ||t,         d| t               d{   }t        |j                  d      xs d      }|dk(  rt        dd	d       n|dk(  rt        d       n|dk(  r(t        |j                  d      xs d	      }|rd| }n|dk(  r|dz  }|dkD  rt        d        	 ddd      d{    yt        d!| d"       	 t        |t        t         d| t               d{   }t        |j                  d      xs d	      }t        |j                  d
      xs d	      }|r|n|}|rt        |       	 ddl}|j!                         }
|
j#                  |       |
j%                  d       |
j'                  d       n|d$k(  rt        |j                  d%      xs d	      }t        |j                  d&      xs d	      }t        |j                  d'      xs t              }t        |j                  d(      xs d	      }|r|s't        j                  d)       	 ddd      d{    yt7        | ||||*       t        d+|        ||||d*cddd      d{    S t/        j2                  d       d{    t)        j*                         |k  rt        d,       	 ddd      d{    y7 7 # t        $ r1}t        j                  d|       Y d}~ddd      d{  7   yd}~ww xY w7 # t        $ r}t        d| d       Y d}~ed}~ww xY w7 # t.        j0                  $ r" t/        j2                  d       d{  7   Y t        $ r?}t        j5                  d|       t/        j2                  d       d{  7   Y d}~d}~ww xY w7 7 # t        $ r Y 5w xY w# t        $ r1}t        j                  d#|       Y d}~ddd      d{  7   yd}~ww xY w7 7 7 7 O# 1 d{  7  sw Y   yxY ww)-z
    Run the interactive iLink QR login flow.

    Returns a credential dict on success, or ``None`` if login fails or times out.
    z'aiohttp is required for Weixin QR loginT	trust_env	connectorNz
?bot_type=)r   r  r"  z#weixin: failed to fetch QR code: %sqrcoder%   qrcode_img_contentz"weixin: QR response missing qrcodeu(   
请使用微信扫描以下二维码：r   )fit)invertu    （终端二维码渲染失败: u-   ，请直接打开上面的二维码链接）z?qrcode=r/   zweixin: QR poll error: %sr  wait.)endflushscanedu%   
已扫码，请在微信里确认...scaned_but_redirectredirect_hosthttps://expiredr    u1   
二维码多次过期，请重新执行登录。u'   
二维码已过期，正在刷新... (z/3)zweixin: QR refresh failed: %s	confirmedilink_bot_id	bot_tokenbaseurlrA  z:weixin: QR confirmed but credential payload was incomplete)r   r   r   r   u!   
微信连接成功，account_id=u   
微信登录超时。)r8   r  r;   ClientSessionr>   r&  ILINK_BASE_URLEP_GET_BOT_QRQR_TIMEOUT_MSr   r   errorrF   r   printr  QRCodeadd_datamakeprint_asciir   	monotonicEP_GET_QR_STATUSr   r,  sleepr   r   )r   r  r^  r  qr_respr   qrcode_value
qrcode_urlqr_scan_datar  qr_qr_excdeadlinecurrent_base_urlrefresh_countstatus_respr  r  _qrcoder   r   r   r   s                          r,   qr_loginr5    s[     DEE$$t?R?TU v vY`		$')?*XJ?(	 G 7;;x06B7%9:@bA
LL=>!v v v( &0z\9:*	mBKK%GGGNN$N' >>#o5)nn)$,- 01,H,	%  2<f=Fcr.8#>?00 #KOOO$D$J K )1-'A$9$" 1$NOIv v vJ @sST $,!/$1?*XJ!G#0	% G $'w{{8'<'B#CL!$W[[1E%F%L"!MJ1;:L!j)0$^^-L1D)d3 ;& !@!FBG
KOOK8>B?{y9K^Lkooo>D"E!LL!]^Iv v vJ $)%# ::,GH"," (&	[v v vf --"""Y nn)\ 	'(mv v v  	LL>Dv v v	v@  	m4WI=jkll	m '' mmA&&& :C@mmA&&&evN" %    LL!@#Fyv v vt uv vf #gv v v vs  5VP3VU?#P9 P6!P9%AU?9VQ6V
U?)A	Q927U?*R"	R
R"BU?V&T 'V,U?<#T6T# AT64A	T&=BU?V$U3%V*$U?VU6V U?8U99U?U?"V-U<.V6P99	Q3Q.U?V'Q*(V.Q33U?6V9	RRU?RU?R""+TSTU?T.TTTU?TU? V#T6&	T3/T62T33T66	U0?U+U?V$U'%V+U00U?3V6V9U?<V?VVVVc                      e Zd ZdZdZdZdZdZd2 fdZd3dZ	e
d4d       Zddd5d	Zd6d
Zd6dZd7dZd7dZd8dZd9dZd9dZed8d       ZdZd:dZd;dZd<dZd=dZd>dZd>dZd?dZd>dZd@dZdAdZ dBdZ!dCdZ"d6dZ#d8d Z$d6d!Z%	 	 	 	 	 	 	 	 	 	 dDd"Z&	 	 	 	 	 	 	 	 	 	 dDd#Z'	 	 dE	 	 	 	 	 	 	 	 	 dFd$Z(dGd%Z)dHdId&Z*dJd'Z+	 	 dE	 	 	 	 	 	 	 	 	 	 	 dKd(Z,	 	 	 dL	 	 	 	 	 	 	 	 	 	 	 dMd)Z-	 	 	 	 dN	 	 	 	 	 	 	 	 	 	 	 	 	 dOd*Z.	 	 	 dL	 	 	 	 	 	 	 	 	 	 	 dPd+Z/	 	 	 dL	 	 	 	 	 	 	 	 	 	 	 dQd,Z0dRd-Z1	 dS	 	 	 	 	 	 	 	 	 dTd.Z2dSdUd/Z3dVd0Z4dWd1Z5 xZ6S )XWeixinAdapterz3Native Hermes adapter for Weixin personal accounts.Ti  Fc           	     
   t         |   |t        j                         |j                  xs i }t        t                     }|| _        t        |      | _	        t               | _        d | _        d | _        d | _        t        t               | _        t        |j%                  d      xs t'        dd            j)                         | _        t        |j,                  xs |j%                  d      xs t'        dd            j)                         | _        t        |j%                  d      xs t'        dt0                    j)                         j3                  d	      | _        t        |j%                  d
      xs t'        dt6                    j)                         j3                  d	      | _        t;        |j%                  d      xs t=        j>                  dd            | _         tC        |j%                  d      xs t=        j>                  dd            | _"        t;        |j%                  d      xs t=        j>                  dd            | _#        tI        jJ                         | _&        tO        dtC        |j%                  d      xs t=        j>                  dd                  | _(        t;        |j%                  d      xs t=        j>                  dd            | _)        t;        |j%                  d      xs t=        j>                  dd            | _*        d| _+        g | _,        t        |j%                  d      xs t=        j>                  d d!            j)                         j[                         | _.        t        |j%                  d"      xs t=        j>                  d#d$            j)                         j[                         | _/        |j%                  d%      }|t=        j>                  d&d      }|j%                  d'      }|t=        j>                  d(d      }| ja                  |      | _1        | ja                  |      | _2        tg        |j%                  d)      xs t=        j>                  d*      d+,      | _4        | jk                  d-d.      | _6        | jk                  d/d0      | _7        i | _8        i | _9        | j*                  r| j.                  stu        || j*                        }|r|t        |j%                  d      xs d      j)                         | _        t        |j%                  d      xs | j4                        j)                         j3                  d	      | _        y y y y )1N)r   r   WEIXIN_ACCOUNT_IDr%   r   WEIXIN_TOKENr   WEIXIN_BASE_URLr   r   WEIXIN_CDN_BASE_URLsend_chunk_delay_secondsWEIXIN_SEND_CHUNK_DELAY_SECONDSz1.5send_chunk_retriesWEIXIN_SEND_CHUNK_RETRIES4send_chunk_retry_delay_seconds%WEIXIN_SEND_CHUNK_RETRY_DELAY_SECONDSz1.0r/   rate_limit_circuit_threshold#WEIXIN_RATE_LIMIT_CIRCUIT_THRESHOLDr  !rate_limit_circuit_window_seconds(WEIXIN_RATE_LIMIT_CIRCUIT_WINDOW_SECONDSz30.0rate_limit_circuit_open_seconds&WEIXIN_RATE_LIMIT_CIRCUIT_OPEN_SECONDS        	dm_policyWEIXIN_DM_POLICYpairinggroup_policyWEIXIN_GROUP_POLICYdisabled
allow_fromWEIXIN_ALLOWED_USERSgroup_allow_fromWEIXIN_GROUP_ALLOWED_USERSsplit_multiline_messagesWEIXIN_SPLIT_MULTILINE_MESSAGESF)r  text_batch_delay_secondsg      @text_batch_split_delay_secondsg      @);superr   r   WEIXINextrarF   r   _hermes_homer   _token_storer   _typing_cache_poll_session_send_session
_poll_taskr   MESSAGE_DEDUP_TTL_SECONDS_dedupr   r   rG   _account_idr   _tokenr  r   	_base_urlr<  _cdn_base_urlr   osgetenv_send_chunk_delay_secondsr  _send_chunk_retries_send_chunk_retry_delay_secondsr   Lock_send_text_gatemax_rate_limit_circuit_threshold"_rate_limit_circuit_window_seconds _rate_limit_circuit_open_seconds_rate_limit_circuit_until_rate_limit_eventsr(   
_dm_policy_group_policy_coerce_list_allow_from_group_allow_fromr  _split_multiline_messages_coerce_float_extra_text_batch_delay_seconds_text_batch_split_delay_seconds_pending_text_batches_pending_text_batch_tasksr   )r   configr[  r   rQ  rS  	persisted	__class__s          r,   r   zWeixinAdapter.__init__  sv   1"/+,'-k:.0>B>B26)6OPuyy6]*EXZ\:]^ddf&,,^%))G*<^
>[]@^_eegUYYz2cjARTb6cdjjlsstwx IIn%_4IK^)_

%'&&+ 	 */II01hRYY?`bg5h*
& $'II*+Zryy9TVY/Z$
  05II67 Iyy@%H0
,  '||~-0		89 I99BCH.
* 38II9: MyyCVL3
/ 16II78 KyyA6J1
- *-&/1eii4`		BTV_8`aggiooq >!:!jbiiH]_i>jkqqsyy{YY|,
#92>J 99%78#!yy)ErJ,,Z8!%!2!23C!D)5II01 <yy:;*
& *.)A)A&*
& 04/G/G,c0
, ?A"BD&DKK+K9I9IJI!)--"8">B?EEG!$Y]]:%>%P$..!Q!W!W!Y!`!`ad!e  %0r.   c                >   ddl }t        | j                  dd      r%| j                  j                  j	                  |      nd}|t        |      S 	 t        |      }|j                  |      r|dk  rt        |      S |S # t        t        f$ r t        |      cY S w xY w)zRead a float from ``config.extra``, guarding against bad/non-finite values.

        The result is fed directly to ``asyncio.sleep()``, so NaN/Inf and
        unparseable values fall back to ``default``.
        r   Nr[  )	mathgetattrr  r[  r   r   	TypeErrorr   isfinite)r   rf   r  r  rI   rq  s         r,   r{  z!WeixinAdapter._coerce_float_extra  s     	.5dkk7D.Q!!%%c*W[=>!	"5\F }}V$
>!	 :& 	">!	"s   A? ?BBc                   | g S t        | t              r>| j                  d      D cg c]#  }|j                         s|j                         % c}S t        | t        t
        t        f      rA| D cg c]5  }t        |      j                         st        |      j                         7 c}S t        |       j                         rt        |       j                         gS g S c c}w c c}w )NrN   )r   rF   r  rG   listtupler   )rI   rv  s     r,   rw  zWeixinAdapter._coerce_list  s    =IeS!-2[[-=NTDJJLNNedE3/027M$3t9??;LCIOO%MM'*5z'7'7'9E
  "#ArA OMs   C$C$2C)C))is_reconnectc                 K   t               s8d}| j                  d|d       t        j                  d| j                  |       y| j
                  s8d}| j                  d|d       t        j                  d| j                  |       y| j                  s8d}| j                  d	|d       t        j                  d| j                  |       y	 | j                  d
| j
                  d      sy	 t        j                  dt                     | _        t        j                  d d d d       }t        j                  dt               |      | _        | j                   j#                  | j                         t%        j&                  | j)                         d      | _        | j-                          | t.        | j
                  <   t        j1                  d| j                  t3        | j                        | j4                         | j6                  dk7  r+t        j                  d| j                  | j6                         y# t        $ r,}t        j                  d| j                  |       Y d }~~d }~ww xY ww)Nz<Weixin startup failed: aiohttp and cryptography are requiredweixin_missing_dependencyF)	retryablez[%s] %sz/Weixin startup failed: WEIXIN_TOKEN is requiredweixin_missing_tokenz4Weixin startup failed: WEIXIN_ACCOUNT_ID is requiredweixin_missing_accountzweixin-bot-tokenzWeixin bot tokenz+[%s] Token lock unavailable (non-fatal): %sTr	  )totalconnectsock_connect	sock_read)r
  r  r  zweixin-poll)namez![%s] Connected account=%s base=%srP  a  [%s] WEIXIN_GROUP_POLICY=%s is set, but QR-login connects an iLink bot identity (e.g. ...@im.bot) which typically cannot be invited into ordinary WeChat groups. iLink usually does not deliver ordinary-group events for these accounts, so group messages may never reach Hermes regardless of this policy. If group delivery doesn't work, the limitation is on the iLink side, not in Hermes.)rC   _set_fatal_errorr   r   r  re  rd  _acquire_platform_lockr   debugr;   r  r>   r_  ClientTimeoutr`  r]  r   r   create_task
_poll_loopra  _mark_connectedr2   r   rL   rf  rv  )r   r  r	  r   _no_aiohttp_timeouts        r,   r  zWeixinAdapter.connect  s	    (*TG!!"=wRW!XNN9dii9{{GG!!"8'U!SNN9dii9LG!!":Gu!UNN9dii9	X../A4;;Pbc d
 %22TM`Mbc
 &33$[_kop$22TM`Mbl  A!!$"2"23!--doo.?mT&*t{{#7HTM]M]D^`d`n`no+NN! 		""	 5  	XLLF		SVWW	Xs1   CI3H; +EI3;	I0!I+%I3+I00I3c                  K   t         j                  | j                  d        d| _        | j                  j                         D ]#  }|j                         r|j                          % | j                  j                          | j                  j                          | j                  rI| j                  j                         s/| j                  j                          	 | j                   d {    d | _
        | j                  r8| j                  j                  s"| j                  j                          d {    d | _        | j                   r8| j                   j                  s"| j                   j                          d {    d | _        | j#                          | j%                          t&        j)                  d| j*                         y 7 # t        j                  $ r Y w xY w7 7 iw)NFz[%s] Disconnected)r2   r   re  _runningr  valuesdonecancelr~  clearra  r   CancelledErrorr_  closedcloser`  _release_platform_lock_mark_disconnectedr   r   r  )r   tasks     r,   
disconnectzWeixinAdapter.disconnect!  sy    4;;-2299; 	D99;	 	""((*&&,,.??4??#7#7#9OO""$oo%% d&8&8&?&?$$**,,,!d&8&8&?&?$$**,,,!##%!'3 &))  - -sd   AG0BG0G .G/G 3AG09G,:A
G0G.AG0G G)&G0(G))G0.G0c           
       K   | j                   J t        | j                  | j                        }t        }d}| j
                  r	 t        | j                   | j                  | j                  ||       d {   }|j                  d      }t        |t              r|dkD  r|}|j                  dd      }|j                  dd      }|dvs|dvr|t        k(  s%|t        k(  st        |||j                  d            rAt        j                  d| j                          t#        j$                  d	       d {    d}|d
z  }t        j'                  d| j                   |||j                  dd      |t(               t#        j$                  |t(        k\  rt*        nt,               d {    |t(        k\  rd}d}t/        |j                  d      xs d      }|r#|}t1        | j                  | j                  |       |j                  d      xs g D ]&  }	t#        j2                  | j5                  |	             ( 	 | j
                  ry y 7 7 7 # t"        j6                  $ r Y y t8        $ rr}
|d
z  }t        j                  d| j                   |t(        |
       t#        j$                  |t(        k\  rt*        nt,               d {  7   |t(        k\  rd}Y d }
~
d }
~
ww xY ww)Nr   )r   r   r-  r"  longpolling_timeout_msr)   r*   >   Nr   r+   z,[%s] Session expired; pausing for 10 minutesiX  r/   z:[%s] getUpdates failed ret=%s errcode=%s errmsg=%s (%d/%d)r%   r(  r*  z[%s] poll error (%d/%d): %s)r_  r  r\  rd  LONG_POLL_TIMEOUT_MSr  r.  rf  re  r   r   r  SESSION_EXPIRED_ERRCODEr-   r   r!  r  r   r)  r   MAX_CONSECUTIVE_FAILURESBACKOFF_DELAY_SECONDSRETRY_DELAY_SECONDSrF   r  r  _process_message_safer  r   )r   r-  r"  consecutive_failuresr  suggested_timeoutr)   r*   new_sync_bufr	  r   s              r,   r  zWeixinAdapter._poll_loop:  s    !!---!$"3"3T5E5EF)
 mm3-!-&&!^^++%)"  %-LL1I$J!/5:Ka:O!2Jll5!,",,y!4i'7)+C66'E\:\4S'8<<PXCYZ%SUYU^U^_%mmC000/0, (A-(NNT		 Xr2,0 "--AUYqAq(=  xK  L  L  L+/GG/0,'($"8<<0A#B#HbI+H"4#4#4d6F6FQ'||F39r MG''(B(B7(KLMW mm" 1 L ))  -$)$:DIIG[]uwz{mm=QUm=m$9  tG  H  H  H'+CC+,(-s   AK)1I 8I9B>I 7I8I >K) A)I )I*I 9K);BI >K)K)I I I K&*K),K&4AK!KK!K)!K&&K)c           
        K   	 | j                  |       d {    y 7 # t        $ rF}t        j                  d| j                  t        |j                  d            |d       Y d }~y d }~ww xY ww)Nz([%s] unhandled inbound error from=%s: %sr  T)exc_info)_process_messager   r   r!  r  rL   r   )r   r	  r   s      r,   r  z#WeixinAdapter._process_message_safev  sn     	K''000 	KLLCTYYPXY`YdYdesYtPuwz  FJL  K  K	Ks6   A2    A2  	A/<A*%A2*A//A2c                  K   | j                   J t        |j                  d      xs d      j                         }|sy || j                  k(  ry t        |j                  d      xs d      j                         }|r| j
                  j                  |      ry |j                  d      xs g }t        |      }|rtd| dt        j                  |j                               j                          }| j
                  j                  |      r"t        j                  d| j                  |       y t        || j                        \  }}|dk(  r>| j                   d	k(  ry | j                   d
k(  r|| j"                  vry | j                   dk(  ry | j%                  |      sy t        |j                  d      xs d      j                         }	|	r'| j&                  j)                  | j                  ||	       t+        j,                  | j/                  ||	xs d              g }
g }|D ]o  }| j1                  ||
|       d {    |j                  d      xs i }|j                  d      }t3        |t4              sU| j1                  ||
|       d {    q |s|
sy | j7                  ||||      }t9        |t;        ||      |||xs d |
|t=        j>                               }t        jA                  d| j                  tC        |      |jD                  tG        |
             |jH                  tJ        jL                  k(  r| jO                  |       y | jQ                  |       d {    y 7 .7 7 
w)Nr  r%   
message_idr6  zcontent:rO   z6[%s] Content-dedup: skipping duplicate message from %sr  rP  	allowlistrM  r7  r  r  )chat_id	chat_typer   	user_name)r   r4  sourceraw_messager  
media_urlsr  	timestampz%[%s] inbound from=%s type=%s media=%d))r_  rF   r   rG   rd  rc  is_duplicater  hashlibmd5r~   	hexdigestr   r  r  r  rv  ry  _is_dm_intake_allowedr]  r   r   r  _maybe_fetch_typing_ticket_collect_mediar   dictbuild_sourcer   r  r   nowr   rL   r  rH   r4  r   r  _enqueue_text_eventhandle_message)r   r	  	sender_idr  r6  r   content_keyr  effective_chat_idr7  media_pathsr  rv  ref_messager  r  events                    r,   r  zWeixinAdapter._process_message|  s*    !!---N39r:@@B	(((\28b9??A
$++22:> KK,2	Y'$YKqT[[]1K1U1U1W0XYK{{''4UW[W`W`bkl'7AQAQ'R$	$!!Z/!![05FdNdNd5d!!Y.++I6GKK8>B?EEG!!$"2"2I}MD;;I}G\X\]^!#!# 	ND%%dKEEE((9-3K"~6H(D)))(KMMM	N K""%	 # 
 1+tD!)T"#lln	
 	;TYYQZH[]c]m]mors~o  	A!1!11$$U+%%e,,,; F N2 -s=   H9M0;M)<:M07M0M,CM0#M.$M0,M0.M0c                    t        j                  dd      j                         dv ryt        j                  dd      j                         dv S )NGATEWAY_ALLOW_ALL_USERSr%   >   r  r  r  TWEIXIN_ALLOW_ALL_USERS)rh  ri  r(   r   s    r,   _open_dm_opted_inzWeixinAdapter._open_dm_opted_in  sD    99.399;?SSyy126<<>BVVVr.   c                    | j                   dk(  ry| j                   dk(  r|| j                  v S | j                   dk(  r| j                         S y)NrP  Fr  openru  rx  r  r   r  s     r,   _is_dm_allowedzWeixinAdapter._is_dm_allowed  sL    ??j(??k) 0 000??f$))++r.   c                    | j                   dk(  ry| j                   dk(  r|| j                  v S | j                   dk(  ry| j                   dk(  r| j                         S y)NrP  Fr  rM  Tr  r  r  s     r,   r  z#WeixinAdapter._is_dm_intake_allowed  s[    ??j(??k) 0 000??i'??f$))++r.   c                     y)zBWeixin gates DM/group access at intake via dm_policy/group_policy.TrB   r  s    r,   enforces_own_access_policyz(WeixinAdapter.enforces_own_access_policy  s     r.   i  c                    ddl m}  ||j                  | j                  j                  j                  dd      | j                  j                  j                  dd      |j                  j                        S )z-Session-scoped key for text message batching.r   )build_session_keygroup_sessions_per_userTthread_sessions_per_userF)r  r  profile)gateway.sessionr  r  r  r[  r   r  )r   r  r  s      r,   _text_batch_keyzWeixinAdapter._text_batch_key  s^    5 LL$(KK$5$5$9$9:SUY$Z%)[[%6%6%:%:;UW\%]LL((	
 	
r.   c                   | j                  |      }| j                  j                  |      }t        |j                  xs d      }|||_        || j                  |<   n|j                  r8|j                  r|j                   d|j                   n|j                  |_        ||_        |j                  rJ|j                  j                  |j                         |j                  j                  |j                         | j                  j                  |      }|r |j                         s|j                          t        j                  | j                  |            | j                  |<   y)a/  Buffer a text event and reset the flush timer.

        When users forward multiple messages or send rapid-fire texts
        via WeChat, each arrives as a separate iLink message. This
        concatenates them and waits for a short quiet period before
        dispatching the combined message.
        r%   Nr  )r  r~  r   rH   r   _last_chunk_lenr  r  r  r  r  r  r   r  _flush_text_batch)r   r  rf   existing	chunk_len
prior_tasks         r,   r  z!WeixinAdapter._enqueue_text_event  s'    ""5)--11#6

(b)	$-E!.3D&&s+zzDLMM8==/EJJ< @W\WaWa'0H$##**5+;+;<$$++E,=,=>3377<
joo/.5.A.A""3'/
&&s+r.   c                  K   t        j                         }	 | j                  j                  |      }|rt	        |dd      nd}|| j
                  k\  r| j                  }n| j                  }t        j                  |       d{    | j                  j                  |      |ur<	 | j                  j                  |      |u r| j                  j                  |d       yy| j                  j                  |d      }|s<	 | j                  j                  |      |u r| j                  j                  |d       yy| j                  |       d{    | j                  j                  |      |u r| j                  j                  |d       yy7 7 B# | j                  j                  |      |u r| j                  j                  |d       w w xY ww)z4Wait for quiet period then dispatch aggregated text.r  r   N)r   current_taskr~  r   r  _SPLIT_THRESHOLDr}  r|  r)  r  r   r  )r   rf   r  pendinglast_lendelayr  s          r,   r  zWeixinAdapter._flush_text_batch  s    ++-	>0044S9GAHww(91=aH4000<<66--&&&--11#6lJ --11#6,F..223= G ..223=E --11#6,F..223= G %%e,,,--11#6,F..223= G ' ---11#6,F..223= GsY   GA,F F!F ';G"F ;G<F FF ;GF F <GGc                  K   |j                  d      }|t        k(  r?| j                  |       d {   }|r#|j                  |       |j                  d       y y |t        k(  r?| j                  |       d {   }|r#|j                  |       |j                  d       y y |t        k(  rB| j                  |       d {   \  }}|r#|j                  |       |j                  |       y y |t        k(  r?| j                  |       d {   }|r#|j                  |       |j                  d       y y y 7 7 7 {7 2w)Nr0  z
image/jpegz	video/mp4z
audio/silk)
r   r  _download_imager  r  _download_videor  _download_filer  _download_voice)r   rv  r  r  	item_typer   mime
voice_paths           r,   r  zWeixinAdapter._collect_media$  s6    HHV$	
"--d33D""4(""<0  *$--d33D""4("";/  )##22488JD$""4(""4(  *$#33D99J"":.""<0  % 4
 4
 9
 :sH   /ED8AE9D;:AED=A
ED?,E;E=E?Ec                v  K   t        |d      }	 t        | j                  | j                  |j	                  d      |j	                  d      xs i j	                  d      xrb t        j                  t        j                  t        |j	                  d      xs i j	                  d                        j                  d      xs |j	                  d      |j	                  d      d       d {   }t        |d	      S 7 # t        $ r+}t        j                  d
| j                  |       Y d }~y d }~ww xY ww)N
image_itemencrypt_query_paramrO  rw   aes_keyry  g      >@r   r   r   ry  r^  .jpgz[%s] image download failed: %s)rw  rz  r_  rg  r   r|   r}   rX   r   rF   r   r   r   r   r   r  r   rv  ru  rY   r   s        r,   r  zWeixinAdapter._download_image;  s     |4	4""!//&+ii0E&F!XXl39r>>xH w$$U]]38N8TRT7Y7YZb7c3d%efmmnuv(99Y':. $	 	D *$77	  	NN;TYYL	sA   D9CD /D 0D ?D9 D 	D6!D1,D91D66D9c           	     b  K   t        |d      }	 t        | j                  | j                  |j	                  d      |j	                  d      |j	                  d      d       d {   }t        |d      S 7 # t        $ r+}t        j                  d| j                  |       Y d }~y d }~ww xY ww)	N
video_itemr  r   ry  g      ^@r  z	video.mp4z[%s] video download failed: %s)
rw  rz  r_  rg  r   r   r   r   r   r  r  s        r,   r  zWeixinAdapter._download_videoM  s      |4	4""!//&+ii0E&F!IIi0:. % D -T;??  	NN;TYYL	sA   B/AA8 %A6&A8 5B/6A8 8	B,!B'"B/'B,,B/c           	       K   |j                  d      xs i }|j                  d      xs i }t        |j                  d      xs d      }t        |      }	 t        | j                  | j
                  |j                  d      |j                  d      |j                  d      d	       d {   }t        ||      |fS 7 # t        $ r/}t        j                  d
| j                  |       d |fcY d }~S d }~ww xY ww)N	file_itemru  	file_namezdocument.binr  r   ry  r]  r  z[%s] file download failed: %s)r   rF   r  rz  r_  rg  r   r   r   r   r  )r   rv  r  ru  r~  r  rY   r   s           r,   r  zWeixinAdapter._download_file]  s     HH[)/R	g&,"y}}[1C^D"8,	4""!//&+ii0E&F!IIi0:. $ D -T8<dBB  	NN:DIIsK:	sI   AC<AC ,B?-C >C<?C 	C9
$C4.C9/C<4C99C<c           	       K   |j                  d      xs i }|j                  d      xs i }|j                  d      ry 	 t        | j                  | j                  |j                  d      |j                  d      |j                  d      d       d {   }t	        |d	      S 7 # t
        $ r+}t        j                  d
| j                  |       Y d }~y d }~ww xY ww)Nr  ru  r   r  r   ry  r]  r  .silkz[%s] voice download failed: %s)	r   rz  r_  rg  r   r   r   r   r  )r   rv  r  ru  rY   r   s         r,   r  zWeixinAdapter._download_voicep  s     XXl+1r
w'-2>>&!	4""!//&+ii0E&F!IIi0:. $ D *$88  	NN;TYYL	sB   =C AB( B&B( %C&B( (	C1!CCCCc                  K   | j                   r| j                  sy | j                  j                  |      ry 	 t	        | j                   | j
                  | j                  ||       d {   }t        |j                  d      xs d      }|r| j                  j                  ||       y y 7 B# t        $ r5}t        j                  d| j                  t        |      |       Y d }~y d }~ww xY ww)Nr   r   r   r7  rB  r%   z [%s] getConfig failed for %s: %s)r_  re  r^  r   rH  rf  rF   r   r   r   r  r  rL   )r   r   r7  r  rB  r   s         r,   r  z(WeixinAdapter._maybe_fetch_typing_ticket  s     !!!!'*	`(""kk+ H  _ = CDM""&&w>   	`LL;TYYQXHY[^__	`sA   6C01B/ *B-+A B/ +C0-B/ /	C-8+C(#C0(C--C0c                D    t        || j                  | j                        S rW   )r  MAX_MESSAGE_LENGTHrz  r   r  s     r,   _split_textzWeixinAdapter._split_text  s"    .T,,d.L.L
 	
r.   c                X    t        d| j                  t        j                         z
        S NrJ  )ro  rs  r   r'  r  s    r,   _rate_limit_cooldown_remainingz,WeixinAdapter._rate_limit_cooldown_remaining  s!    3669IIJJr.   c                >    t        d| j                         dd      S )Nz4iLink sendmessage rate limited; cooldown active for z.1fs)r  r  r  s    r,   _rate_limit_errorzWeixinAdapter._rate_limit_error  s)    B4CfCfChilBmmno
 	
r.   c                    | j                   dk  ry t        | j                  t        j                         | j                   z         | _        y )Nr   )rr  ro  rs  r   r'  r  s    r,   _open_rate_limit_circuitz&WeixinAdapter._open_rate_limit_circuit  s>    00A5),**NNtDDD*
&r.   c                ^   t        j                         }|| j                  z
  }| j                  D cg c]
  }||k\  s	| c}| _        | j                  j	                  |       t        | j                        | j                  k\  r#| j                          | j                         dkD  S yc c}w )zDRecord a genuine iLink rate limit and return True if breaker opened.r   F)	r   r'  rq  rt  r  rH   rp  r  r  )r   r  window_starttss       r,   _record_rate_limit_eventz&WeixinAdapter._record_rate_limit_event  s    nnTDDD040G0G"^"2Q]K]2"^&&s+t&&'4+M+MM))+6681<< #_s
   
B*B*c                F    | j                   j                          d| _        y r  )rt  r  rs  r  s    r,   _reset_rate_limit_circuitz'WeixinAdapter._reset_rate_limit_circuit  s    %%'),&r.   c                  K   | j                   4 d{    | j                  ||||       d{    ddd      d{    y7 27 7 	# 1 d{  7  sw Y   yxY ww)uh  Send a single text chunk with per-chunk retry and backoff.

        On session-expired errors (errcode -14), automatically retries
        *without* ``context_token`` — iLink accepts tokenless sends as a
        degraded fallback, which keeps cron-initiated push messages working
        even when no user message has refreshed the session recently.
        Nr  chunkr7  r3  )rn  _send_text_chunk_locked)r   r  r!  r7  r3  s        r,   _send_text_chunkzWeixinAdapter._send_text_chunk  so      '' 	 	..+#	 /   	 	 		 	 	 	sS   A!AA!AAAA! A
A!A
A!AAAA!c                 K   d}d}t        | j                  dz         D ]  }| j                         dkD  r| j                         	 t	        | j
                  | j                  | j                  ||||       d{   }|rt        |t              r|j                  d      }	|j                  d      }
|	|	dvs|
|
dvr|	t        k(  xs' |
t        k(  xs t        |	|
|j                  d	            }|r~|s||rzd
}d}| j                  j                  j                  | j                  j!                  | j"                  |      d       t$        j'                  d| j(                  t+        |             V|	t,        k(  xs	 |
t,        k(  }|r|j                  d	      xs |j                  d      xs d}t/        d|	 d|
 d|       }| j1                         r| j                         } n|| j                  k\  r n| j2                  dz  }t$        j'                  d| j(                  t+        |      |       t5        j6                  |       d{    4|j                  d	      xs |j                  d      xs d}t/        d|	 d|
 d|       | j9                           y |J |7 .7 ^# t:        $ r}|}|| j                  k\  rY d}~ +| j2                  |dz   z  }t$        j'                  d| j(                  t+        |      |dz   | j                  dz   ||       |dkD  rt5        j6                  |       d{  7   Y d}~'d}~ww xY ww)zDSend a text chunk while holding the adapter-wide outbound text gate.NFr/   r   r   r   r>  r   r7  r3  r)   r*   >   r   r+   Tz;[%s] session expired for %s; retrying without context_tokenr8  zrate limitedz$iLink sendmessage rate limited: ret=z	 errcode=z errmsg=r    z8[%s] rate limited for %s; backing off %.1fs before retryr&   ziLink sendmessage error: ret=zA[%s] send chunk failed to=%s attempt=%d/%d, retrying in %.2fs: %s)rangerk  r  r  r?  r`  rf  re  r   r  r   r  r-   r]  r   r   r   rd  r   r   r  rL   r'   r  r  rl  r   r)  r  r   )r   r  r!  r7  r3  
last_errorretried_without_tokenattemptrespr)   r*   is_session_expiredis_rate_limitedr+   r  r   s                   r,   r"  z%WeixinAdapter._send_text_chunk_locked  sc     +/
 %T559: Q	.G224q8,,..N.*&&!^^++"/'  JtT2((5/C"hhy1GCtOATY`hlYl#:: W&*AAW4S'488HCUV + .6KP]481,0M --4488 $ 1 1 6 6t7G7G QSW #NN ] $		8G+< %  #55 =&*<< ( +%)XXh%7%\488E?%\nF *6"Fse9U\T]]eflem n*J  $<<>-1-C-C-E
 %&$*B*BB %#'#G#G!#KD"NN Z $		8G+<d #*--"555$!%(!3!Ytxx!Y/*;C5	'RZ[aZbc  ..0CQ	.d %%%_l 6  . 
d666;;w{KWIIW%aK,,q0 !8!-----.s   AM3J-:J(;C8J-3M5A2J-'M)J-8M:AJ-J+J-MAJ-	M(J-+J--	M6MMA-M:L=;M MMMc                   K    j                   r j                  st        dd      S  j                  j	                   j
                        }d } j                  |      \  }} j                  |      } j                  |      \  }	}
 j                  |
      \  }} j                  |      }h dh dh ddd fd}	 |D ]  \  }}	  |||       d {     |D ]  }	  ||d	       d {      j                   j!                  |            D cg c]  }|s|j#                         s| }}t%        |      D ]  \  }}dt'        j(                         j*                   } j-                  |||       d {    |}|t/        |      dz
  k  sX j0                  dkD  sht3        j4                   j0                         d {     t        d|      S 7 # t        $ r-}t        j                  d j                  ||       Y d }~Rd }~ww xY w7 -# t        $ r-}t        j                  d
 j                  ||       Y d }~rd }~ww xY wc c}w 7 7 # t        $ rK}t        j7                  d j                  t9              |       t        dt;        |            cY d }~S d }~ww xY ww)NFNot connectedsuccessr!  >   .m4a.mp3.ogg.wav.flac.opus>   .3gp.avi.mkv.mov.mp4.webm>   .gifr  .png.jpeg.webpc                f  K   t        |       j                  j                         }|s|v rj                  |        d {    y |v rj	                  |        d {    y |v rj                  |        d {    y j                  |        d {    y 7 d7 E7 &7 w)N)r  
audio_pathmetadata)r  
video_pathrC  )r  
image_pathrC  )r  	file_pathrC  )r   suffixr(   
send_voice
send_videosend_image_filesend_document)	r   is_voiceext_AUDIO_EXTS_IMAGE_EXTS_VIDEO_EXTSr  rC  r   s	      r,   _deliver_mediaz*WeixinAdapter.send.<locals>._deliver_mediaH  s     t*##))+C3+-oog$QYoZZZ#oog$QYoZZZ#**7tV^*___((DS[(\\\ [Z_\sH   AB1B) B1%B+& B1B-B1#B/$B1+B1-B1/B1z%[%s] media delivery failed for %s: %s)rL  z*[%s] local file delivery failed for %s: %shermes-weixin-r   r/   r   Tr0  r  z[%s] send failed to=%s: %sF)r   rF   rL  r  r   r   )r`  re  r   r]  r   rd  extract_mediafilter_media_delivery_pathsextract_imagesextract_local_filesfilter_local_delivery_pathsr   r   r   r  r  format_messagerG   	enumerateuuiduuid4hexr#  rH   rj  r   r)  r!  rL   rF   )r   r  r  reply_torC  r7  last_message_idmedia_filescleaned_content_image_cleanedlocal_filesfinal_contentrQ  
media_pathrL  r   rF  r  r  idxr!  r3  rN  rO  rP  s   ``  `                  @@@r,   sendzWeixinAdapter.send1  s     !!e?CC))--d.>.>H)- (,'9'9''B$_66{C..?=%)%=%=m%L"]66{CHG@		] 		]	=(3 h$
Hh(X>>>h ) l	l(UCCCl "&!1!1$2E2Em2T!UiAYZ_`_f_f_haiFi'/ 
H
U,TZZ\-=-=,>?	++#"/'	 ,    #,Vq(T-K-Ka-O!--(F(FGGG
H dGG1 ?  hNN#JDIIWacfggh D  lNN#OQUQZQZ\egjkkl j H 	=LL5tyy(7BSUXYe3s8<<	=s   CKI. G0"G-#G0'I. /H,<H)=H,$I. %I%-I%>I%AI. I*I. 'I. 7"I. I,I. ,K-G00	H&9"H!I. !H&&I. )H,,	I"5"II. I""	I. ,I. .	K7A J=7K8K=KKc                :  K   | j                   j                  |      }|r|S | j                  r| j                  sy| j                  j                  | j
                  |      }	 t        | j                  | j                  | j                  ||       d{   }t        |j                  d      xs d      }|r| j                   j                  ||       |S 	 y7 D# t        $ r5}t        j                  d| j                  t        |      |       Y d}~yd}~ww xY ww)u  Return a valid typing ticket, refreshing from getConfig if expired.

        The iLink typing ticket has a 600-second TTL.  When a long-running
        session exceeds that window the cached ticket evicts, and both
        ``send_typing`` and ``stop_typing`` silently no-op — leaving the
        WeChat client stuck showing the typing indicator forever.  This
        method transparently refreshes the ticket so the stop signal can
        always be delivered.
        Nr  rB  r%   z,[%s] typing ticket refresh failed for %s: %s)r^  r   r`  re  r]  rd  rH  rf  rF   r   r   r   r  r  rL   )r   r  r   r7  r  rB  r   s          r,   _ensure_typing_ticketz#WeixinAdapter._ensure_typing_tickett  s     ##''0M!! ))--d.>.>H	(""kk+ H  _ = CDM""&&w>$$  !  	LL>		8G,c  	sI   AD"1C CAC DDC 	D#+DDDDc                x  K   | j                   r| j                  sy | j                  |       d {   }|sy 	 t        | j                   | j                  | j                  ||t
               d {    y 7 D7 # t        $ r5}t        j                  d| j                  t        |      |       Y d }~y d }~ww xY ww)Nr   r   r  rB  r  z#[%s] typing start failed for %s: %s)r`  re  rk  rE  rf  TYPING_STARTr   r   r  r  rL   )r   r  rC  rB  r   s        r,   send_typingzWeixinAdapter.send_typing  s     !!"88AA
	c""kk"+#  	 B  	cLL>		8T[K\^abb	cJ   .B:A5B:6A9 /A70A9 4B:7A9 9	B7+B2-B:2B77B:c                x  K   | j                   r| j                  sy | j                  |       d {   }|sy 	 t        | j                   | j                  | j                  ||t
               d {    y 7 D7 # t        $ r5}t        j                  d| j                  t        |      |       Y d }~y d }~ww xY ww)Nrm  z"[%s] typing stop failed for %s: %s)r`  re  rk  rE  rf  TYPING_STOPr   r   r  r  rL   )r   r  rB  r   s       r,   stop_typingzWeixinAdapter.stop_typing  s     !!"88AA
	b""kk"+"  	 B  	bLL=tyy(SZJ[]`aa	brp  c                  K   |j                  d      r| j                  |       d {   }d}nR|j                  dd      }t        j                  j                  |      st        j                  j                  |      }d}	 | j                  ||||       d {   |r:|r7t        j                  j                  |      r	 t        j                  |       S S S S 7 7 B# t        $ r Y S w xY w# |rI|rFt        j                  j                  |      r&	 t        j                  |       w # t        $ r Y w w xY ww w w xY ww)N)zhttp://r  Tzfile://r%   F)captionrC  )r   _download_remote_mediareplacerh  r   isabsabspathrK  r   unlinkr   )r   r  	image_urlru  r_  rC  rF  cleanups           r,   
send_imagezWeixinAdapter.send_image  s      78"99)DDIG!)))R8I77==+GGOOI6	G	++GYZb+cc9	)BIIi( *C9w E d
   9	)BIIi(  *C9ws   &D?CAD?C/ CC/ #D?C D?C/  	C,)D?+C,,D?/$D<D*)D<*	D63D<5D66D<<D?c                L   K   ~~| j                  ||||       d {   S 7 w)N)r  rF  ru  rC  )rK  )r   r  rE  ru  r_  rC  kwargss          r,   rJ  zWeixinAdapter.send_image_file  s;      f'' 	 ( 
 
 	
 
s   $"$c                h  K   ~~~~| j                   r| j                  st        dd      S 	 | j                  |||xs d       d {   }t        d|      S 7 # t        $ rK}	t
        j                  d| j                  t        |      |	       t        dt        |	            cY d }	~	S d }	~	ww xY ww)NFr.  r/  r%   TrS  z#[%s] send_document failed to=%s: %s
r`  re  r   
_send_filer   r   r!  r  rL   rF   )
r   r  rF  ru  r  r_  rC  r  r  r   s
             r,   rK  zWeixinAdapter.send_document  s      x6!!e?CC	=#w	7=bQQJdzBB R 	=LL>		8T[K\^abe3s8<<	=sG   *B2A AA B2A 	B/$A B*$B/%B2*B//B2c                `  K   | j                   r| j                  st        dd      S 	 | j                  |||xs d       d {   }t        d|      S 7 # t        $ rK}t
        j                  d| j                  t        |      |       t        dt        |            cY d }~S d }~ww xY ww)NFr.  r/  r%   TrS  z [%s] send_video failed to=%s: %sr  )r   r  rD  ru  r_  rC  r  r   s           r,   rI  zWeixinAdapter.send_video  s      !!e?CC	=#w
GMrRRJdzBB S 	=LL;TYYQXHY[^_e3s8<<	=sG   &B.A AA B.A 	B+ A B& B+!B.&B++B.c                h  K   | j                   r| j                  st        dd      S |xs d}	 | j                  |||d       d {   }t        d|      S 7 # t        $ rK}t
        j                  d| j                  t        |      |       t        dt        |            cY d }~S d }~ww xY ww)	NFr.  r/  z[voice message as attachment]Tforce_file_attachmentrS  z [%s] send_voice failed to=%s: %sr  )	r   r  rB  ru  r_  rC  fallback_captionr  r   s	            r,   rH  zWeixinAdapter.send_voice  s      !!e?CC
 #E&E
	=# &*	  /   J dzBB  	=LL;TYYQXHY[^_e3s8<<	=sG   ,B2A AA B2A 	B/$A B*$B/%B2*B//B2c                   K   ddl m}  |      st        d        j                  J  fd}t	        j
                   |       d       d {   }t        j                  dd      d         j                  xs d	}t        j                  d
|      5 }|j                  |       |j                  cd d d        S 7 n# 1 sw Y   y xY ww)Nr   )is_safe_urlz&Blocked unsafe URL (SSRF protection): c                    K   j                   j                        4 d {   } | j                          | j                          d {   cd d d       d {    S 7 =7 7 	# 1 d {  7  sw Y   y xY wwrW   )r`  r   ra  rV  )r  r   r  s    r,   	_do_fetchz7WeixinAdapter._download_remote_media.<locals>._do_fetch0  sh     ))--c2 - -h))+%]]_,- - -,- - - -sV    A<A!A<$A'A#A'A<A%A<#A'%A<'A9-A0.A95A<r"   r  rE   r/   z.binF)deleterG  )tools.url_safetyr  r   r`  r   r!  r   r  rG  tempfileNamedTemporaryFilewriter  )r   r  r  r  rY   rG  handles   ``     r,   rv  z$WeixinAdapter._download_remote_media'  s     03EcUKLL!!---	- %%ik2>>ciiQ'*+22<f((fE 	LL;;	 	 ?	 	s+   ACCACC9CC
Cc                x  K   | j                   | j                  J t        |      j                         }| j	                  ||      \  }}t        j                  d      }t        j                  d      }	t        |      }
t        j                  |      j                         }t        | j                   | j                  | j                  ||||
|t        |
      |	j                         
       d {   }t!        |j#                  d      xs d      }t!        |j#                  d      xs d      }t%        ||	      }|r|}n(|rt'        | j(                  ||      }nt+        d|       t-        | j                   ||       d {   }| j.                  j#                  | j0                  |      }t3        j4                  |	j                         j7                  d	            j9                  d	      }||t        |      |
t        |      j:                  |d
}|t<        k(  r |j?                  d      rd|d<   d|d<   d|d<    |di |}d }|rhdtA        jB                         j                   }tE        | j                   | j                  | j                  || jG                  |      ||       d {    dtA        jB                         j                   }tI        | j                   | j                  tJ        dd||tL        tN        |gd|rd|ini i| j                  tP               d {    |S 7 ?7 7 7 w)Nr  rk   )	r   r   r  rJ  r   rK  rL  rM  rQ  r   r%   upload_full_urlz@getUploadUrl returned neither upload_param nor upload_full_url: )rn   rX  rw   )r  aes_key_for_apiciphertext_sizeplaintext_sizer~  rL  r
  r  encode_typei]  sample_ratebits_per_samplerR  r%  r8  r2  r7  r)  rB   ))r`  re  r   
read_bytes_outbound_media_builderrz   	token_hexr{   rH   r  r  r  rR  rf  rt   r^  rF   r   rh   r   rg  r  r\  r]  rd  r|   r}   r~   r   r  MEDIA_VOICErm   r\  r]  r?  rZ  r#  r<  r9  r:  r=  )r   r  r   ru  r  re   rJ  item_builderr   r   rK  rL  upload_responser   r  rn   rX  r   r7  r  item_kwargs
media_itemr`  s                          r,   r  zWeixinAdapter._send_file:  s%     !!-$++2IIIJ))+	#'#?#?\q#?#r 
L##B'%%b)i.[[+557
 /^^++!!%g.{{}!
 
 ?..~>D"Eo112CDJK(G<

 (J(););\7SJ!abqarstt&8!!'
 !

 ))--d.>.>H !**7;;=+?+?+HIPPQXY#8.":%T
$
 $w)?)*K&).K&-/K)*!0K0
 .tzz|/?/?.@AO""kk((1+)   +4::<+;+;*<=^^$$&")!0$0%5", <I7b
 ++%!
 	
 	
$ e
4!
6	
sK   C/L:1L02BL:L3D(L:/L60A9L:)L8*L:3L:6L:8L:c                    t        j                  |      d   xs d}|j                  d      r	t        d fS |j                  d      r	t        d fS |j                  d      r|s	t        d fS |j                  d	      r	t        d
 fS t        d fS )Nr   rU  r  c                 4    t         | d   | d   dd| d   ddS )Nr  r  r/   r  r   encrypt_typer  )ru  mid_size)r0  r  )r  kws    r,   <lambda>z7WeixinAdapter._outbound_media_builder.<locals>.<lambda>  s8    " 022G/H#%&7#8()
 !##4 5
. r.   r  c                 x    t         | d   | d   dd| d   | j                  dd      | j                  dd	      d
dS )Nr  r  r/   r  r  play_lengthr   rL  r%   )ru  
video_sizer  	video_md5)r0  r  )r  r   r  s    r,   r  z7WeixinAdapter._outbound_media_builder.<locals>.<lambda>  sT    " 022G/H#%&7#8()
 #%%6"7#%66-#;!#b!9	. r.   r
  c            	         t         | d   | d   dd| j                  d      | j                  d      | j                  d      | j                  dd	      d
dS )Nr  r  r/   r  r  r  r  playtimer   )ru  r  r  r  r  )r0  r  )r  r   r  s    r,   r  z7WeixinAdapter._outbound_media_builder.<locals>.<lambda>  sb    " 022G/H#%&7#8()
 $&66-#8')vv.?'@#%66-#8 "z1 5
. r.   r  c                 N    t         | d   | d   dd| d   t        | d         ddS 	Nr  r  r/   r  r~  r  )ru  r  rH   )r0  r  r  rF   r  s    r,   r  z7WeixinAdapter._outbound_media_builder.<locals>.<lambda>  sD    ! 022G/H#%&7#8()
 "$Jr"234- r.   c                 N    t         | d   | d   dd| d   t        | d         ddS r  r  r  s    r,   r  z7WeixinAdapter._outbound_media_builder.<locals>.<lambda>  sD     ,..C+D!"34$%
  
^2./0)
 r.   )r|  r}  r   MEDIA_IMAGEMEDIA_VIDEOrm   r  
MEDIA_FILE)r   r   r  r  s       r,   r  z%WeixinAdapter._outbound_media_builder  s    ##D)!,J0J??8$ 
! 
 
 ??8$ !   ==!*? !   ??8$      
 
 	
r.   c                @   K   |j                  d      rdnd}|||dS w)Nz	@chatroomr  r  )r  r0  r  )rm   )r   r  r  s      r,   get_chat_infozWeixinAdapter.get_chat_info  s'     &//<G$	wGGs   c                0    |yt        t        |            S )Nr%   )r  r  r  s     r,   rZ  zWeixinAdapter.format_message  s    ?34Nw4WXXr.   )r  r   )rf   rF   r  r   r   r   )rI   r   r   	List[str])r  r  r   r  )r   r   )r	  r1   r   r   r   r  )r  rF   r   r  )r  r   r   rF   )r  r   r   r   )rf   rF   r   r   )rv  r1   r  r  r  r  r   r   )rv  r1   r   r   )rv  r1   r   zTuple[Optional[str], str])r   rF   r7  r   r   r   r  rF   r   r  )r   r   )r   r  )
r  rF   r!  rF   r7  r   r3  rF   r   r   )NN)
r  rF   r  rF   r_  r   rC  Optional[Dict[str, Any]]r   r   )r  rF   r   r   rW   )r  rF   rC  r  r   r   )r  rF   r   r   )r  rF   r{  rF   ru  rF   r_  r   rC  r  r   r   )NNN)r  rF   rE  rF   ru  r   r_  r   rC  r  r   r   )NNNN)r  rF   rF  rF   ru  r   r  r   r_  r   rC  r  r   r   )r  rF   rD  rF   ru  r   r_  r   rC  r  r   r   )r  rF   rB  rF   ru  r   r_  r   rC  r  r   r   )r  rF   r   rF   rT  )
r  rF   r   rF   ru  rF   r  r  r   rF   )r   rF   r  r  )r  rF   r   r1   )r  r   r   rF   )7r   r   r   r   supports_code_blockssplits_long_messagesr  SUPPORTS_MESSAGE_EDITINGr   r{  staticmethodrw  r  r  r  r  r  r  r  r  propertyr  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r#  r"  ri  rk  ro  rs  r}  rJ  rK  rI  rH  rv  r  r  r  rZ  __classcell__)r  s   @r,   r7  r7  s  s   =  %Sfj& B B 5: .`42:-xKF-PW
	   

:>*1.$ &&`&

K


	-  	
 %  
.^ ^ 	^
 %^ ^ 
^H #'-1A=A= A=  	A=
 +A= 
A=F#Jc$b. #'-1  	
   + 
: "&"&-1

 
 	

  
 +
 

* "&#'"&-1== = 	=
 !=  = += 
=0 "&"&-1== = 	=
  = += 
=* "&"&-1== = 	=
  = += 
=60 ',`` ` 	`
  $` 
`DC
JHYr.   r7  )ra  c                  K   t        | j                  d      xs t        dd            j                         }t        | j                  d      xs t        dt                    j                         j                  d      }t        | j                  d      xs t        dt                    j                         j                  d      }t        |xs | j                  d	      xs t        d
d            j                         }|sddiS |sddiS t        t        t                           }	|	j                  |       |	j                  ||      }
t        j                  |      }t        |dd      }|$|!|j                  s|j                  t        j                         u rd}|j!                  |      }|r7|j#                  ||       d{   }|j$                  sdd|j&                   iS |xs g D ]  \  }}t)        |      j*                  j-                         }|dv r|j/                  ||       d{   }n|j1                  ||       d{   }|j$                  rodd|j&                   ic S  dd||r|j2                  ndt5        |
      dS t7        j8                  dt;                     4 d{   }t=        t?        d|i tA        | xs i       |||d            }||_!        ||_"        ||_#        ||_$        ||_%        ||_&        |	|_'        d}|j!                  |      }|rH|j#                  ||       d{   }|j$                  s"dd|j&                   icddd      d{    S |xs g D ]  \  }}t)        |      j*                  j-                         }|dv r|j/                  ||       d{   }n|j1                  ||       d{   }|j$                  rodd|j&                   ic cddd      d{    S  dd||r|j2                  ndt5        |
      dcddd      d{    S 7 v7 7 7 7 	7 7 7 y7 K7 # 1 d{  7  sw Y   yxY ww)z
    One-shot send helper for ``send_message`` and cron delivery.

    This bypasses the long-poll adapter lifecycle and uses the raw API directly.
    r   r9  r%   r   r;  r   r   r<  r   r:  r!  zGWeixin token missing. Configure WEIXIN_TOKEN or platforms.weixin.token.z\Weixin account ID missing. Configure WEIXIN_ACCOUNT_ID or platforms.weixin.extra.account_id.r`  NzWeixin send failed: >   .bmpr=  r  r>  r?  r@  zWeixin media send failed: Tr   )r0  platformr  r  context_token_usedr	  )r   r   r   )enabledr   r[  )(rF   r   r   rG   r  r   r<  r   r   r   r2   r  r  _loopr   get_running_looprZ  ri  r0  r!  r   rG  r(   rJ  rK  r  r  r;   r  r>   r7  r   r  r`  _sessionre  rd  rf  rg  r]  )r[  r   r  r	  ra  r   r   r   resolved_tokentoken_storer7  live_adaptersend_sessionlast_resultcleanedrg  	_is_voicerM  r  adapters                       r,   send_weixin_directr    st     UYY|,S
;NPR0STZZ\J599Z(YJ7H.,YZ``biijmnHuyy0jJ?TVi4jkqqszz{~LV%))G"4V
>SU8VW]]_Nbccwxx#C(9$:;K
#OOJ8M!%%n5L<$?L \%= ''""g&>&>&@@,0--g6 , 1 1'7 CCK&&#78I8I7J!KLL%0%6B 	S!J	z"))//1CHH$0$@$@*$UU$0$>$>w
$SS&&#=k>O>O=P!QRR	S  4?+00T"&}"5
 	
 $$t?R?TU +
 +
Y`$5;B'", ($0		
 !("'($ ,*,0((1 'Wg >>K&&#78I8I7J!KL5+
 +
 +
8 &1%6B 	S!J	z"))//1CHH$+$;$;GZ$PP$+$9$9':$NN&&#=k>O>O=P!QRRG+
 +
 +
8	S  4?+00T"&}"5
K+
 +
 +
+ D VS+
0 ?1+
> QNC+
 +
 +
 +
 +
s   GQ3QA)Q39Q:Q3QQ3&AQ3<Q=Q3 BQQ	 Q)Q35Q6Q3;AQQQQQ0QQ3QQ3Q3Q3?Q Q3Q3Q3Q3QQ3QQQ3Q3Q0$Q'%Q0,Q3)r)   'Optional[int]'r*   r  r+   z'Optional[str]'r   r  )r   z Optional['aiohttp.TCPConnector']r  )   )rI   r   rJ   r  r   rF   )rT   r1   r   rF   )rk   )rY   rX   rZ   r  r   rX   )re   rX   rf   rX   r   rX   )rn   rX   rf   rX   r   rX   )rs   r  r   r  rZ  r  )r   r   r   rF   r   zDict[str, str])r   rF   r   r   )r   rF   r   rF   r   r   )r   rF   r   rF   r   rF   r   rF   r   rF   r   r   )r   rF   r   rF   r   r  )r   rF   r   rF   r   rF   )r   rF   r   rF   r   rF   r   rF   )r   rF   r   rX   )r	  r1   r   rF   r   zTuple[str, str])r  'aiohttp.ClientSession'r   rF   r  rF   rT   r1   r   r   r"  r  r   r1   )
r  r  r   rF   r  rF   r"  r  r   r1   )r  r  r   rF   r   rF   r-  rF   r"  r  r   r1   )r  r  r   rF   r   rF   r>  rF   r   rF   r7  r   r3  rF   r   r1   )r  r  r   rF   r   rF   r  rF   rB  rF   r  r  r   r   )r  r  r   rF   r   rF   r   rF   r7  r   r   r1   )r  r  r   rF   r   rF   r  rF   rJ  r  r   rF   rK  r  rL  rF   rM  r  rQ  rF   r   r1   )r  r  rn   rX   rX  rF   r   rF   )r  r  r  rF   r^  r   r   rX   )r  rF   r   r   )rv  r1   rf   rF   r   r1   )r  r  r   rF   r   r   r   r   ry  r   r^  r   r   rX   )r~  rF   r   rF   )r  rF   r   r  )r  rF   r   rF   r  )r  rF   r   r  )r  rF   r   r  )r  rF   r  r  r   r  rT  )r  rF   r  r  r  r  r   r  )T)rI   r   r  r  r   r  )r6  zList[Dict[str, Any]]r   rF   )r  r  r   rF   r   r   )r   rF   r   rF   r   rF   )r   rF   r   rF   r-  rF   r   r   )r   rF   r  rF   r^  r  r   zOptional[Dict[str, str]])r[  r1   r   r   r  rF   r	  rF   ra  z Optional[List[Tuple[str, bool]]]r   r1   )r   
__future__r   r   r|   r  rR   loggingr|  rh  r  rz   rx   r  r  r   r\  r   pathlibr   typingr   r   r   r	   r
   urllib.parser   r   	getLoggerr   r   r  r;   r8   r7   cryptography.hazmat.backendsr   &cryptography.hazmat.primitives.ciphersr   r   r   rA   gateway.configr   r   gateway.platforms.helpersr   gateway.platforms.baser   r   r   r   r   r   r   hermes_constantsr   utilsr   agent.secret_scoper   r  r<  r   r   r   r+  r<  rC  rG  rP  r  r(  r  r=  rD  r   r  r  r  r  r'   rb  r-   r  r  r  r  r2   __annotations__r>   r;  r  r  r  r  MSG_TYPE_USERr9  r:  rn  rr  compiler  r  r  _MARKDOWN_LINK_RErC   rL   rU   r\   rh   rp   rt   r   r   r   r   r   r   r   r   r   r   r   r   r  r#  r&  r.  r?  rE  rH  rR  r\  rc  	frozensetrk  rs  rw  rz  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r5  r7  r  rB   r.   r,   <module>r     s  
 #       	 	         3 3 (			8	$ 

<PP 4 9   - # )0= 3 ')'%, *0         5	5#25<K5	5 
!# #-& 	

	
 RZZ01
RSBJJ+,	BJJ9: 2
J-
J	(H
0<   	
   
./m /md5 5(pR8D$D D 	D
 D D D D.D$D D 	D
 D D0C$C C 	C
 C C C*"$" " 	"
 	" " !" " "J$  	
    
.$  	
  ! *$  	
        @=$= = 	=
 	=@ "	K$K 
K 	K
 K )2
) ~ 

(4$  )	
    2K5%<%&P#0L$,N(CK0 ;@)N)N!)N37)N)NX$:A; 	DD D 	D
 DNuY' uY|# 59b
b
 b
 	b

 b
 2b
 b
CF  G  OFJEs$   9L  L% 	L"!L"%L87L8