
    p#j                   B   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mZ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 	 ddlZdZn# e$ r d	ZdZY nw xY w	 ddlZdZn# e$ r d	ZdZY nw xY wdd
lm Z m!Z! ddl"m#Z# ddl$m%Z%m&Z&m'Z'm(Z(m)Z)m*Z*  ej+        e,          Z-dZ.dZ/dZ0dZ1dZ2dZ3dZ4dZ5dZ6dZ7dZ8e0e1hZ9e9e2hz  Z:dZ;dZ<dZ=dZ>g dZ?dZ@dZAdZBdZCd ZDeDZEd!ZFd"ZGd#hZHd=d&ZId>d*ZJd?d-ZKd@d0ZL G d1 d2e%          ZMd3ZNd4ZOd5ZPd6ZQd7ZReRd8dAd<ZSdS )Ba.  
WeCom (Enterprise WeChat) platform adapter.

Uses the WeCom AI Bot WebSocket gateway for inbound and outbound messages.
The adapter focuses on the core gateway path:

- authenticate via ``aibot_subscribe``
- receive inbound ``aibot_msg_callback`` events
- send outbound markdown messages via ``aibot_send_msg``
- upload outbound media via ``aibot_upload_media_*`` and send native attachments
- best-effort download of inbound image/file attachments for agent context

Configuration in config.yaml:
    platforms:
      wecom:
        enabled: true
        extra:
          bot_id: "your-bot-id"          # or WECOM_BOT_ID env var
          secret: "your-secret"          # or WECOM_SECRET env var
          websocket_url: "wss://openws.work.weixin.qq.com"
          dm_policy: "open"              # open | allowlist | disabled | pairing
          allow_from: ["user_id_1"]
          group_policy: "open"           # open | allowlist | disabled
          group_allow_from: ["group_id_1"]
          groups:
            group_id_1:
              allow_from: ["user_id_1"]
    )annotationsN)datetimetimezone)Path)AnyDictListOptionalTuple)unquoteurlparseTF)PlatformPlatformConfig)MessageDeduplicator)BasePlatformAdapterMessageEventMessageType
SendResultcache_document_from_bytescache_image_from_byteszwss://openws.work.weixin.qq.comaibot_subscribeaibot_msg_callbackaibot_callbackaibot_event_callbackaibot_send_msgaibot_respond_msgpingaibot_upload_media_initaibot_upload_media_chunkaibot_upload_media_finishi  g      4@g      .@      >@)      
      <   i  i   i    i  @i   d   	audio/amrreturnboolc                     t           ot          S )z2Check if WeCom runtime dependencies are available.)AIOHTTP_AVAILABLEHTTPX_AVAILABLE     </home/ubuntu/.hermes/hermes-agent/gateway/platforms/wecom.pycheck_wecom_requirementsr1   l   s    00r/   valuer   	List[str]c                X   | g S t          | t                    rd |                     d          D             S t          | t          t          t
          f          rd | D             S t          |                                           r"t          |                                           gng S )z0Coerce config values into a trimmed string list.Nc                ^    g | ]*}|                                 |                                 +S r.   )strip.0items     r0   
<listcomp>z _coerce_list.<locals>.<listcomp>v   s-    JJJTZZ\\J

JJJr/   ,c                    g | ]D}t          |                                          #t          |                                          ES r.   )strr6   r7   s     r0   r:   z _coerce_list.<locals>.<listcomp>x   s=    IIIds4yy7H7HID		!!IIIr/   )
isinstancer=   splitlisttuplesetr6   )r2   s    r0   _coerce_listrC   q   s    }	% KJJS)9)9JJJJ%$s+,, JIIeIIII#&u::#3#3#5#5=CJJ2=r/   rawr=   c                    t          |                                           }t          j        dd|t          j                  }t          j        dd|t          j                  }|                                S )z7Normalize allowlist entries such as ``wecom:user:foo``.z^wecom: )flagsz^(user|group):)r=   r6   resub
IGNORECASE)rD   r2   s     r0   _normalize_entryrK   |   sZ    HHNNEF:r5>>>EF$b%r}EEEE;;==r/   entriestargetc                    t          |                                                                          }| D ]2}t          |                                          }|dk    s||k    r dS 3dS )z4Case-insensitive allowlist match with ``*`` support.*TF)r=   r6   lowerrK   )rL   rM   normalized_targetentry
normalizeds        r0   _entry_matchesrT      sv    F))++1133  %e,,2244

.? ? ?44 !@5r/   c                      e Zd ZdZeZdZdZd fdZdd	ZddZ	ddZ
ddZddZddZddZddZddZddZddZefddZeefdd!Zedd#            Zedd$            Zedd(            Zdd)Zdd,Zdd-Zdd/Zedd1            Zdd3Z dd7Z!edd:            Z"edd;            Z#eddd?            Z$eddB            Z%eddE            Z&eddJ            Z'e(ddK            Z)ddMZ*ddOZ+ddPZ,ddRZ-ddSZ.ddUZ/eddW            Z0eddX            Z1eddY            Z2eddd^            Z3edd`            Z4e5ddb            Z6edde            Z7ddhZ8eddj            Z9	 dddmZ:	 dddnZ;ddpZ<ddrZ=ddtZ>dduZ?	 dddwZ@	 	 	 dddzZA	 	 ddd|ZB	 	 	 ddd~ZC	 	 dddZD	 	 	 dddZE	 	 dddZF	 	 dddZGdddZHddZI xZJS )WeComAdapterzAWeCom AI Bot adapter backed by a persistent WebSocket connection.Fi<  configr   c                @   t                                          |t          j                   |j        pi }t          |                    d          pt          j        dd                    	                                | _
        t          |                    d          pt          j        dd                    	                                | _        t          |                    d          p.|                    d          pt          j        dt                              	                                pt          | _        t          |                    d	          pt          j        d
d                    	                                                                | _        t!          |                    d          p)|                    d          pt          j        dd                    | _        t          |                    d          pt          j        dd                    	                                                                | _        t!          |                    d          p|                    d                    | _        t)          |                    d          t*                    r|                    d          ni | _        d | _        d | _        d | _        d | _        d | _        i | _        t;          t<                    | _        i | _         tC          t          j        dd                    | _"        tC          t          j        dd                    | _#        i | _$        i | _%        tM          j'                    j(        | _)        i | _*        d S )Nbot_idWECOM_BOT_IDrF   secretWECOM_SECRETwebsocket_urlwebsocketUrlWECOM_WEBSOCKET_URL	dm_policyWECOM_DM_POLICYopen
allow_from	allowFromWECOM_ALLOWED_USERSgroup_policyWECOM_GROUP_POLICYgroup_allow_fromgroupAllowFromgroups)max_size%HERMES_WECOM_TEXT_BATCH_DELAY_SECONDSz0.6+HERMES_WECOM_TEXT_BATCH_SPLIT_DELAY_SECONDSz2.0)+super__init__r   WECOMextrar=   getosgetenvr6   _bot_id_secretDEFAULT_WS_URL_ws_urlrP   
_dm_policyrC   _allow_from_group_policy_group_allow_fromr>   dict_groups_session_ws_http_client_listen_task_heartbeat_task_pending_responsesr   DEDUP_MAX_SIZE_dedup_reply_req_idsfloat_text_batch_delay_seconds_text_batch_split_delay_seconds_pending_text_batches_pending_text_batch_tasksuuiduuid4hex
_device_id_last_chat_req_ids)selfrW   rq   	__class__s      r0   ro   zWeComAdapter.__init__   s   000"599X..O")NB2O2OPPVVXX599X..O")NB2O2OPPVVXXIIo&& @yy((@y.??
 
 %''	$ $	 	 eii44\	BSU[8\8\]]cceekkmm
 (IIl## 4yy%%4y.33
 
 !>!:!:!ebiH\^d>e>effllnnttvv!-eii8J.K.K.juyyYiOjOj!k!k.889L9Ld.S.S[uyy***Y[;?@D;?487;=?)>BBB.0 */ry9`bg/h/h)i)i&/4RY?lns5t5t/u/u,>@"BD&*,,*24r/   r)   r*   c                  K   t           s=d}|                     d|d           t                              d| j        |           dS t
          s=d}|                     d|d           t                              d| j        |           dS | j        r| j        s=d	}|                     d
|d           t                              d| j        |           dS 	 ddlm	} t          j        dd |                      | _        |                                  d{V  |                                  t          j        |                                           | _        t          j        |                                           | _        t                              d| j        | j                   dS # t.          $ r}d| }|                     d|d           t                              d| j        |d           |                                  d{V  | j        r&| j                                         d{V  d| _        Y d}~dS d}~ww xY w)z$Connect to the WeCom AI Bot gateway.z+WeCom startup failed: aiohttp not installedwecom_missing_dependencyT)	retryablez![%s] %s. Run: pip install aiohttpFz)WeCom startup failed: httpx not installedz[%s] %s. Run: pip install httpxz@WeCom startup failed: WECOM_BOT_ID and WECOM_SECRET are requiredwecom_missing_credentialsz[%s] %sr   )platform_httpx_limitsr!   )timeoutfollow_redirectslimitsNz[%s] Connected to %szWeCom startup failed: wecom_connect_errorz[%s] Failed to connect: %s)exc_info)r,   _set_fatal_errorloggerwarningnamer-   ru   rv   %gateway.platforms._http_client_limitsr   httpxAsyncClientr   _open_connection_mark_connectedasynciocreate_task_listen_loopr   _heartbeat_loopr   inforx   	Exceptionerror_cleanup_wsaclose)r   messager   excs       r0   connectzWeComAdapter.connect   s       	CG!!"<gQU!VVVNN>	7SSS5 	AG!!"<gQU!VVVNN<diQQQ5| 	4< 	XG!!"=wRV!WWWNN9di9995	SSSSSS % 1t<Q<Q<S<S! ! !D '')))))))))  """ ' 3D4E4E4G4G H HD#*#6t7K7K7M7M#N#ND KK.	4<HHH4 	 	 	4s44G!!"7D!QQQLL5ty#PTLUUU""$$$$$$$$$  )'..000000000$(!55555	s   CF- -
I	7BII	Nonec                  K   d| _         |                                  | j        rD| j                                         	 | j         d{V  n# t          j        $ r Y nw xY wd| _        | j        rD| j                                         	 | j         d{V  n# t          j        $ r Y nw xY wd| _        |                     t          d                     | 	                                 d{V  | j
        r&| j
                                         d{V  d| _
        | j                                         t                              d| j                   dS )zDisconnect from WeCom.FNzWeCom adapter disconnectedz[%s] Disconnected)_running_mark_disconnectedr   cancelr   CancelledErrorr   _fail_pending_responsesRuntimeErrorr   r   r   r   clearr   r   r   r   s    r0   
disconnectzWeComAdapter.disconnect   s     !!! 	%$$&&&''''''''')    $D 	( '')))*********)   #'D $$\2N%O%OPPP          	%#**,,,,,,,,, $D'33333s#   A AA
B B*)B*c                   K   | j         r+| j         j        s| j                                          d{V  d| _         | j        r+| j        j        s| j                                         d{V  d| _        dS )z)Close the live websocket/session, if any.N)r   closedcloser   r   s    r0   r   zWeComAdapter._cleanup_ws  s      8 	#DHO 	#(.."""""""""= 	(!5 	(-%%'''''''''r/   c                B  K   |                                   d{V  t          j        d          | _        | j                            | j        t          dz  t                     d{V | _        | 	                    d          }| 
                    t          d|i| j        | j        | j        dd	           d{V  |                     |           d{V }|                    d
d          }|dvr+|                    dd          }t#          | d| d          dS )z-Open and authenticate a websocket connection.NT)	trust_envr"   )	heartbeatr   	subscribereq_id)rY   r[   	device_idcmdheadersbodyerrcoder      Nr   errmsgzauthentication failedz
 (errcode=))r   aiohttpClientSessionr   
ws_connectrx   HEARTBEAT_INTERVAL_SECONDSCONNECT_TIMEOUT_SECONDSr   _new_req_id
_send_jsonAPP_CMD_SUBSCRIBEru   rv   r   _wait_for_handshakerr   r   )r   r   auth_payloadr   r   s        r0   r   zWeComAdapter._open_connection  s              -===11L014+ 2 
 
 
 
 
 
 
 
 !!+..oo($f-"l"l!%  

 

 
	
 
	
 
	
 
	
 
	
 
	
 
	
 "55f========""9a00)##!%%h0GHHF&>>G>>>??? $#r/   r   r=   Dict[str, Any]c                N  K   | j         st          d          t          j                                                    t
          z   }	 |t          j                                                    z
  }|dk    rt          d          t          j        | j                                         |           d{V }|j	        t          j        j        k    r|                     |j                  }|s|                    d          t           k    r|                     |          |k    r|S t$                              d| j        |                    d                     nH|j	        t          j        j        t          j        j        t          j        j        hv rt          d	          `)
z'Wait for the subscribe acknowledgement.zWebSocket not initializedTr   z5Timed out waiting for WeCom subscribe acknowledgementr   Nr   z"[%s] Ignoring pre-auth payload: %sz,WeCom websocket closed during authentication)r   r   r   get_running_looptimer   TimeoutErrorwait_forreceivetyper   	WSMsgTypeTEXT_parse_jsondatarr   APP_CMD_PING_payload_req_idr   debugr   CLOSEDCLOSEERROR)r   r   deadline	remainingmsgpayloads         r0   r   z WeComAdapter._wait_for_handshake6  s     x 	<:;;;+--22447NN	S 7#;#=#=#B#B#D#DDIA~~"#Z[[[()9)9););YOOOOOOOOOCx7,111**3844 ;;u%%55''00F::"NA49gkkZ_N`N`aaaag/68I8OQXQbQhiii"#QRRR!	Sr/   c                  K   d}| j         r{	 |                                  d{V  d}nR# t          j        $ r Y dS t          $ r3}| j         sY d}~dS t
                              d| j        |           |                     t          d                     t          t          |t          t                    dz
                     }|dz  }t          j        |           d{V  	 |                                  d{V  d}|                                  t
                              d| j                   n8# t          $ r+}t
                              d| j        |           Y d}~nd}~ww xY wY d}~nd}~ww xY w| j         ydS dS )z6Read websocket events forever, reconnecting on errors.r   Nz[%s] WebSocket error: %szWeCom connection interrupted   z[%s] Reconnectedz[%s] Reconnect failed: %s)r   _read_eventsr   r   r   r   r   r   r   r   RECONNECT_BACKOFFminlensleepr   r   r   )r   backoff_idxr   delayreconnect_excs        r0   r   zWeComAdapter._listen_loopN  s     m 	ZZ''))))))))))    Z Z Z} FFFFF949cJJJ,,\:X-Y-YZZZ)#k3?P;Q;QTU;U*V*VWq mE*********Z//111111111"#K((***KK 2DI>>>>  Z Z ZNN#>	=YYYYYYYYZZ m 	Z 	Z 	Z 	Z 	ZsL   , E;
E;E6BE6(AD98E69
E.!E)$E6)E..E66E;c                D  K   | j         st          d          | j        r| j         r| j         j        s| j                                          d{V }|j        t          j        j        k    r8| 	                    |j
                  }|r|                     |           d{V  nX|j        t          j        j        t          j        j        t          j        j        t          j        j        hv rt          d          | j        r| j         r| j         j        dS dS dS dS dS dS )z2Read websocket frames until the connection closes.zWebSocket not connectedNzWeCom websocket closed)r   r   r   r   r   r   r   r   r   r   r   _dispatch_payloadr   r   r   CLOSING)r   r   r   s      r0   r   zWeComAdapter._read_eventsi  sa     x 	:8999m 	= 	= 	=((********Cx7,111**3844 :00999999999g/5w7H7OQXQbQhjqj{  kD  E  E  E"#;<<< m 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	= 	=r/   c                  K   	 | j         rt          j        t                     d{V  | j        r| j        j        r:	 |                     t          d|                     d          ii d           d{V  n8# t          $ r+}t                              d| j        |           Y d}~nd}~ww xY w| j         dS dS # t          j        $ r Y dS w xY w)z)Send lightweight application-level pings.Nr   r   r   z[%s] Heartbeat send failed: %s)r   r   r   r   r   r   r   r   r   r   r   r   r   r   )r   r   s     r0   r   zWeComAdapter._heartbeat_loopw  sO     	- Sm$>?????????x 48? 	S//#/(0$2B2B62J2J'K$&           ! S S SLL!A49cRRRRRRRRS - S S S S S % 	 	 	DD	s:   :B< 9A9 8B< 9
B.!B)$B< )B..
B< <CCr   c                  K   |                      |          }t          |                    d          pd          }|rY|| j        v rP|t          vrG| j                            |          }|r)|                                s|                    |           dS |t          v r|                     |           d{V  dS |t          t          hv rdS t                              d| j        |p|           dS )z!Route inbound websocket payloads.r   rF   Nz#[%s] Ignoring websocket payload: %s)r   r=   rr   r   NON_RESPONSE_COMMANDSdone
set_resultCALLBACK_COMMANDS_on_messager   APP_CMD_EVENT_CALLBACKr   r   r   )r   r   r   r   futures        r0   r   zWeComAdapter._dispatch_payload  s     %%g..'++e$$*++ 	f 777CG\<\<\,0088F +fkkmm +!!'***F###""7+++++++++F<!7888F:DIs~gVVVVVr/   r   r   c                    t          | j                                                  D ]I\  }}|                                s|                    |           | j                            |d           JdS )z%Fail all outstanding request futures.N)r@   r   itemsr   set_exceptionpop)r   r   r   r  s       r0   r   z$WeComAdapter._fail_pending_responses  sv    "4#:#@#@#B#BCC 	6 	6NFF;;== *$$S)))#''5555	6 	6r/   c                   K   | j         r| j         j        rt          d          | j                             |           d{V  dS )z0Send a raw JSON frame over the active websocket. WeCom websocket is not connectedN)r   r   r   	send_json)r   r   s     r0   r   zWeComAdapter._send_json  sZ      x 	C48? 	CABBBh  )))))))))))r/   r   r   r   r   c                  K   | j         r| j         j        rt          d          |                     |          }t	          j                                                    }|| j        |<   	 |                     |d|i|d           d{V  t	          j	        ||           d{V }|| j        
                    |d           S # | j        
                    |d           w xY w)z6Send a JSON request and await the correlated response.r  r   r   Nr   )r   r   r   r   r   r   create_futurer   r   r   r	  )r   r   r   r   r   r  responses          r0   _send_requestzWeComAdapter._send_request  s     x 	C48? 	CABBB!!#&&)++99;;*0'	6//#8V:LVZ"["[\\\\\\\\\$-fgFFFFFFFFFH#''5555D#''5555s   *>C C!reply_req_idc                  K   | j         r| j         j        rt          d          t          |pd                                          }|st          d          t          j                                                    }|| j	        |<   	 | 
                    |d|i|d           d{V  t          j        ||           d{V }|| j	                            |d           S # | j	                            |d           w xY w)z<Send a reply frame correlated to an inbound callback req_id.r  rF   zreply_req_id is requiredr   r   Nr   )r   r   r   r=   r6   
ValueErrorr   r   r  r   r   r   r	  )r   r  r   r   r   normalized_req_idr  r  s           r0   _send_reply_requestz WeComAdapter._send_reply_request  sB      x 	C48? 	CABBB 23399;;  	97888)++99;;5; 12	A//3D(EtTT         %-fgFFFFFFFFFH#''(94@@@@D#''(94@@@@s   	>C# #D prefixc                <    |  dt          j                    j         S )N-)r   r   r   )r  s    r0   r   zWeComAdapter._new_req_id  s    --4:<<+---r/   c                    |                      d          }t          |t                    r$t          |                     d          pd          S dS )Nr   r   rF   )rr   r>   r}   r=   )r   r   s     r0   r   zWeComAdapter._payload_req_id  sI    ++i((gt$$ 	4w{{8,,2333rr/   rD   r   Optional[Dict[str, Any]]c                    	 t          j        |           }n,# t          $ r t                              d|            Y d S w xY wt          |t                    r|nd S )Nz!Failed to parse WeCom payload: %r)jsonloadsr   r   r   r>   r}   )rD   r   s     r0   r   zWeComAdapter._parse_json  sh    	jooGG 	 	 	LL<cBBB44	 %Wd33=ww=s    %A A c                  K   |                     d          }t          |t                    sdS t          |                     d          p,|                     |          pt          j                    j                  }| j        	                    |          r#t                              d| j        |           dS |                     ||                     |                     t          |                     d          t                    r|                     d          ni }t          |                     d          pd                                          }t          |                     d          p|                                          }|s"t                              d	| j                   dS t          |                     d
          pd                                          dk    }|r;|                     ||          s$t                              d| j        ||           dS n8|                     |          s#t                              d| j        |           dS |                     ||                     |                     |                     |          \  }}	|r*|r(t)          j        dd|                                          }|                     |           d{V \  }
}|                     |||          }t1          |	o|p|
          }|s|	r|
s|	}|s$|
s"t                              d| j                   dS |                     ||rdnd|pd|pd          }t5          ||||||
||rd| nd|r|	ndt7          j        t:          j                  
  
        }|t>          j         k    r"| j!        dk    r| "                    |           dS | #                    |           d{V  dS )z0Process an inbound WeCom message callback event.r   Nmsgidz![%s] Duplicate message %s ignoredfromuseridrF   chatidz&[%s] Missing chat id, skipping messagechattypegroupz+[%s] Group %s / sender %s blocked by policyz#[%s] DM sender %s blocked by policyz^@\S+\s*z [%s] Empty WeCom message skippeddm)chat_id	chat_typeuser_id	user_namequote:)tz)
textmessage_typesourceraw_message
message_id
media_urlsmedia_typesreply_to_message_idreply_to_text	timestampr   )$rr   r>   r}   r=   r   r   r   r   r   is_duplicater   r   r   _remember_reply_req_idr6   rP   _is_group_allowed_is_dm_allowed_remember_chat_req_id_extract_textrH   rI   _extract_media_derive_message_typer*   build_sourcer   r   nowr   utcr   r   r   _enqueue_text_eventhandle_message)r   r   r   msg_idsender	sender_idr&  is_groupr,  
reply_textr1  r2  r-  has_reply_contextr.  events                   r0   r  zWeComAdapter._on_message  s      {{6""$%% 	FTXXg&&[$*>*>w*G*G[4:<<K[\\;##F++ 	LL<diPPPF##FD,@,@,I,IJJJ%/0@0@$%G%GO&!!!R

8,,23399;;	dhhx((5I66<<>> 	LLA49MMMFtxx
++1r2288::gE 	))'9== JDIW^`ijjj $$Y// 	LL>	9UUUF
 	""7D,@,@,I,IJJJ--d33j  	9 	96+r4006688D(,(;(;D(A(A"A"A"A"A"A"A
K00t[II !D1CEE 	
 	: 	D 	J 	LL;TYGGGF""!)3ggt%'4	 # 
 
 %!#5F P 1 1 1 1D(9C**tlhl333
 
 
 ;+++0NQR0R0R$$U+++++%%e,,,,,,,,,,,r/   rI  r   c                    ddl m}  ||j        | j        j                            dd          | j        j                            dd                    S )z-Session-scoped key for text message batching.r   )build_session_keygroup_sessions_per_userTthread_sessions_per_userF)rL  rM  )gateway.sessionrK  r.  rW   rq   rr   )r   rI  rK  s      r0   _text_batch_keyzWeComAdapter._text_batch_key=  sg    555555  L$(K$5$9$9:SUY$Z$Z%)[%6%:%:;UW\%]%]
 
 
 	
r/   c                   |                      |          }| j                            |          }t          |j        pd          }|||_        || j        |<   nw|j        r$|j        r|j         d|j         n|j        |_        ||_        |j        r>|j                            |j                   |j                            |j                   | j	                            |          }|r(|
                                s|                                 t          j        |                     |                    | j	        |<   dS )zBuffer a text event and reset the flush timer.

        When WeCom splits a long user message at 4000 chars, the chunks
        arrive within a few hundred milliseconds.  This merges them into
        a single event before dispatching.
        rF   N
)rO  r   rr   r   r,  _last_chunk_lenr1  extendr2  r   r   r   r   r   _flush_text_batch)r   rI  keyexisting	chunk_len
prior_tasks         r0   rA  z WeComAdapter._enqueue_text_eventF  sN    ""5))-11#66
(b))	$-E!.3D&s++z bDLM a8= @ @EJ @ @ @W\Wa'0H$ ?#**5+;<<<$++E,=>>> 377<<
 	 joo// 	 .5.A""3''/
 /
&s+++r/   rU  c                  K   t          j                    }	 | j                            |          }|rt	          |dd          nd}|| j        k    r| j        }n| j        }t          j        |           d{V  | j	                            |          |ur<	 | j	                            |          |u r| j	        
                    |d           dS dS | j        
                    |d          }|s<	 | j	                            |          |u r| j	        
                    |d           dS dS t                              d|t          |j        pd                     |                     |           d{V  | j	                            |          |u r| j	        
                    |d           dS dS # | j	                            |          |u r| j	        
                    |d           w w xY w)zWait for the quiet period then dispatch the aggregated text.

        Uses a longer delay when the latest chunk is near WeCom's 4000-char
        split point, since a continuation chunk is almost certain.
        rR  r   Nz)[WeCom] Flushing text batch %s (%d chars)rF   )r   current_taskr   rr   getattr_SPLIT_THRESHOLDr   r   r   r   r	  r   r   r   r,  rB  )r   rU  rZ  pendinglast_lenr   rI  s          r0   rT  zWeComAdapter._flush_text_batchd  s=      +--	>044S99GAHOww(91===aH4000<6-&&&&&&&&& -11#66lJJ -11#66,FF.223===== GF .223==E  -11#66,FF.223===== GF KK;S)r**   %%e,,,,,,,,,-11#66,FF.223===== GFt-11#66,FF.223==== Gs   A?F1 F1 +AF1 1:G+Tuple[str, Optional[str]]c                	   g }d}t          |                     d          pd                                          }|dk    r.|                     d          }t          |t                    r|ni }|                    d          }t          |t
                    r|ng }|D ]}t          |t                    st          |                    d          pd                                          dk    r{|                    d          }	t          |	t                    r|	ni }
t          |
                    d          pd                                          }|r|                    |           ΐnt          |                     d          t                    r|                     d          ni }
t          |
                    d          pd                                          }|r|                    |           |dk    rt          |                     d          t                    r|                     d          ni }t          |                    d          pd                                          }|r|                    |           |d	k    rt          |                     d	          t                    r|                     d	          ni }t          |                    d
          pd                                          }|r|                    |           t          |                     d          t                    r|                     d          ni }t          |                    d          pd                                          }|dk    rxt          |                    d          t                    r|                    d          ni }t          |                    d          pd                                          pd}n}|dk    rwt          |                    d          t                    r|                    d          ni }t          |                    d          pd                                          pd}d                    d |D                                                       |fS )z;Extract plain text and quoted text from a callback payload.NmsgtyperF   mixedmsg_itemr,  contentvoiceappmsgtitlequoterQ  c              3     K   | ]}||V  	d S Nr.   )r8   parts     r0   	<genexpr>z-WeComAdapter._extract_text.<locals>.<genexpr>  s'      ==$=======r/   )	r=   rr   rP   r>   r}   r@   r6   appendjoin)r   
text_partsrG  ra  
_raw_mixedrb  
_raw_itemsr  r9   	_raw_text
text_blockrd  voice_block
voice_textrf  rg  rh  
quote_type
quote_textquote_voices                       r0   r;  zWeComAdapter._extract_text  sH    !#
$(
dhhy))/R006688g'**J",Z">">FJJBE:..J",Z">">FJJBE 3 3!$-- txx	**0b117799VCC $ 0 0I.8D.I.I!QrJ!*..";";"ArBBHHJJG 3"))'2223 .88H8H$-O-OW&)))UWJ*..339r::@@BBG +!!'***'!!3=dhhw>O>OQU3V3V^dhhw///\^ !;!;!ArBBHHJJ
 2%%j111 (""/9$((8:L:Ld/S/S[(+++Y[FJJw//5266<<>> -%%e,,,%/0A0A4%H%HP!!!b9--344::<<
.869J9JD.Q.QY6***WYJZ^^I66<"==CCEEMJJ7""0:599W;M;Mt0T0T\%))G,,,Z\K[__Y77=2>>DDFFN$Jyy==*=====CCEEzQQr/   Tuple[List[str], List[str]]c                  K   g }g }g }t          |                    d          pd                                          }|dk    r|                    d          }t          |t                    r|ni }|                    d          }t          |t
                    r|ng }	|	D ]}
t          |
t                    st          |
                    d          pd                                          }|dk    rEt          |
                    d          t                    r|                    d|
d         f           nQt          |                    d          t                    r|                    d|d         f           |dk    rEt          |                    d          t                    r|                    d|d         f           |dk    rt          |                    d          t                    r|d         }t          |                    d          t                    r|                    d|d         f           nEt          |                    d          t                    r|                    d|d         f           t          |                    d          t                    r|                    d          ni }t          |                    d          pd                                          }|dk    rFt          |                    d          t                    r|                    d|d         f           nK|dk    rEt          |                    d          t                    r|                    d|d         f           |D ]R\  }}|                     ||           d	{V }|r/|\  }}|                    |           |                    |           S||fS )
z=Best-effort extraction of inbound media to local cache paths.ra  rF   rb  rc  imagefilerf  rh  N)r=   rr   rP   r>   r}   r@   rm  _cache_media)r   r   media_pathsr2  refsra  rp  rb  rq  r  r9   	item_typerf  rh  rv  kindrefcachedpathcontent_types                       r0   r<  zWeComAdapter._extract_media  s     !#!#13dhhy))/R006688g'**J",Z">">FJJBE:..J",Z">">FJJBE : :!$--  3 3 9r::@@BB	''Jtxx7H7H$,O,O'KK$w- 8999: $((7++T22 6Wd7m4555&  Z0@0@$%G%G VT&\2333(""z$((82D2Dd'K'K"hfjj00$77 <KK 89999

7 3 3T:: <KK&/ :;;;%/0A0A4%H%HP!!!b9--344::<<
  Z		'0B0BD%I%I KK%.122226!!j61B1BD&I&I!KKv/000 	1 	1ID#,,T377777777F 1%+"l""4(((""<000K''r/   r  mediaOptional[Tuple[str, str]]c                @  K   d|v rN|                     d          r8	 |                     |d                   }n:# t          $ r-}t                              d| j        ||           Y d}~dS d}~ww xY w|dk    rv|                     |          }	 t          ||          |                     |d          fS # t          $ r,}t          
                    d| j        |           Y d}~dS d}~ww xY wt          |                     d          p|                     d	          pd
          }t          ||          t          j        |          d         pdfS t          |                     d          pd                                          }|sdS 	 |                     |t"                     d{V \  }}n;# t          $ r.}t                              d| j        |||           Y d}~dS d}~ww xY wt          |                     d          pd                                          }	|	rS	 |                     ||	          }n;# t          $ r.}t                              d| j        |||           Y d}~dS d}~ww xY wt          |                     d          pd                              dd          d                                         pd}
|dk    r|                     ||
|                     |                    }	 t          ||          |
p|                     |d          fS # t          $ r-}t          
                    d| j        ||           Y d}~dS d}~ww xY w|                     ||                     d          |
          }t          ||          |
fS )z=Cache an inbound image/file/media reference to local storage.base64z)[%s] Failed to decode %s base64 media: %sNr{  z
image/jpeg)fallbackz![%s] Rejected non-image bytes: %sfilenamer   
wecom_filer   application/octet-streamurlrF   	max_bytesz&[%s] Failed to download %s from %s: %saeskeyz%[%s] Failed to decrypt %s from %s: %scontent-type;r   z)[%s] Rejected non-image bytes from %s: %scontent-disposition)rr   _decode_base64r   r   r   r   _detect_image_extr   _mime_for_extr  r   r=   r   	mimetypes
guess_typer6   _download_remote_bytesABSOLUTE_MAX_BYTES_decrypt_file_bytesr?   _guess_extension_guess_filename)r   r  r  rD   r   extr  r  r   aes_keyr  s              r0   r}  zWeComAdapter._cache_media  s      u8!4!4))%/::   H$)UY[^___ttttt w,,S11 1#s;;T=O=OPS^j=O=k=kkk!      NN#F	SVWWW44444  599Z00UEIIf4E4EUVVH,S(;;Y=QRZ=[=[\]=^=|b|||%))E""(b))//11 	4	!%!<!<SL^!<!_!_______LC 	 	 	LLA49dTWY\]]]44444	 eii))/R006688 	..sG<<   DdiQUWZ\_```ttttt 7;;~66<"==CCCKKANTTVVtZt7??''\DDZDZ[^D_D_'``C-c3779wI[I[\_jvI[IwIwww   JDIWZ\_```ttttt ''W[[9N-O-OQ]^^(h77EEsu   ; 
A2"A--A2&B8 8
C.!C))C.%F7 7
G/#G**G/+I 
I:#I55I:(L* *
M!4"MM!r   bytesc                    |                      dd          d                                         }t          j        |          S )Nr;   r   )r?   r6   r  	b64decode)r   r   s     r0   r  zWeComAdapter._decode_base64  s7    **S!$$R(..00(((r/   c                    |                      d          rdS |                      d          rdS |                      d          rdS |                      d          r| dd	         d
k    rdS dS )Ns   PNG

z.pngs   z.jpg)s   GIF87as   GIF89az.gifs   RIFF      s   WEBPz.webp
startswith)r   s    r0   r  zWeComAdapter._detect_image_ext$  s    ??/00 	6???++ 	6??122 	6??7## 	QrT
g(=(=7vr/   r  r  r  c                f    t           j                            |                                 |          S rj  )r  	types_maprr   rP   )r  r  s     r0   r  zWeComAdapter._mime_for_ext0  s#    "&&syy{{H===r/   r  r  c                    |rt          j        |          nd }|r|S t          t          |           j                  j        }|r|S |S rj  )r  guess_extensionr   r   r  suffix)r  r  r  r  path_exts        r0   r  zWeComAdapter._guess_extension4  sT    9EOi'5554 	J*++2 	Or/   content_dispositionOptional[str]c                    |r,t          j        d|          }|r|                    d          S t          t	          |           j                  j        pd}d|vrt          j        |          pd}| | }|S )Nzfilename="?([^";]+)"?r   document.z.bin)	rH   searchr$  r   r   r  r   r  r  )r  r  r  matchr   r  s         r0   r  zWeComAdapter._guess_filename>  s     	&I68KLLE &{{1~~%HSMM&'',:
d??+L99CVC>C>>Dr/   r,  r2  r3   r   c                V   t          d |D                       rt          j        S t          d |D                       r|rt          j        nt          j        S t          |                     d          pd                                          dk    rt          j        S t          j        S )z+Choose the normalized inbound message type.c              3  @   K   | ]}|                     d           V  dS ))zapplication/ztext/Nr  r8   mtypes     r0   rl  z4WeComAdapter._derive_message_type.<locals>.<genexpr>N  s1      TTuu 9::TTTTTTr/   c              3  @   K   | ]}|                     d           V  dS )image/Nr  r  s     r0   rl  z4WeComAdapter._derive_message_type.<locals>.<genexpr>P  s0      CCeu))CCCCCCr/   ra  rF   re  )	anyr   DOCUMENTr   PHOTOr=   rr   rP   VOICE)r   r,  r2  s      r0   r=  z!WeComAdapter._derive_message_typeK  s     TTTTTTT 	(''CC{CCCCC 	C'+B;##1BBtxx	""(b))//11W<<$$r/   c                    dS )zAWeCom gates DM/group access at intake via dm_policy/group_policy.Tr.   r   s    r0   enforces_own_access_policyz'WeComAdapter.enforces_own_access_policyZ  s	     tr/   rE  c                `    | j         dk    rdS | j         dk    rt          | j        |          S dS )NdisabledF	allowlistT)ry   rT   rz   )r   rE  s     r0   r9  zWeComAdapter._is_dm_allowed_  s9    ?j((5?k))!$"2I>>>tr/   r&  c                    | j         dk    rdS | j         dk    rt          | j        |          sdS |                     |          }t	          |                    d          p|                    d                    }|rt          ||          S dS )Nr  Fr  rc   rd   T)r{   rT   r|   _resolve_group_cfgrC   rr   )r   r&  rE  	group_cfgsender_allows        r0   r8  zWeComAdapter._is_group_allowedf  s    ++5,,^DDZ\c5d5d,5++G44	#IMM,$?$?$]9==Q\C]C]^^ 	;!,	:::tr/   c                   t          | j        t                    si S || j        v r-t          | j        |         t                    r| j        |         S |                                }| j                                        D ]K\  }}t          |t
                    r1|                                |k    rt          |t                    r|c S L| j                            d          }t          |t                    r|ni S )NrO   )r>   r~   r}   rP   r  r=   rr   )r   r&  loweredrU  r2   wildcards         r0   r  zWeComAdapter._resolve_group_cfgr  s    $,-- 	Idl""z$,w2G'N'N"<((--//,,,.. 	 	JC#s## 		w(>(>:eUYCZCZ(><##C((%h55=xx2=r/   r0  c                   t          |pd                                          }t          |pd                                          }|r|sd S || j        |<   t          | j                  t          k    rX| j                            t          t          | j                                       t          | j                  t          k    Vd S d S )NrF   )r=   r6   r   r   r   r	  nextiter)r   r0  r   normalized_message_idr  s        r0   r7  z#WeComAdapter._remember_reply_req_id~  s     #J$4" 5 5 ; ; = ="--3355$ 	,= 	F5F12$%&&77##Dd.A)B)B$C$CDDD $%&&777777r/   c                   t          |pd                                          }t          |pd                                          }|r|sdS || j        |<   t          | j                  t          k    rX| j                            t          t          | j                                       t          | j                  t          k    VdS dS )u  Cache the most recent inbound req_id per chat.

        Used as a fallback reply target when we need to send into a group
        without an explicit ``reply_to`` — WeCom AI Bots are blocked from
        APP_CMD_SEND in groups and must use APP_CMD_RESPONSE bound to some
        prior req_id. Bounded like _reply_req_ids so long-running gateways
        don't leak memory across many chats.
        rF   N)r=   r6   r   r   r   r	  r  r  )r   r&  r   normalized_chat_idr  s        r0   r:  z"WeComAdapter._remember_chat_req_id  s     !B//5577"--3355! 	): 	F6G 23$)**^;;#''T$2I-J-J(K(KLLL $)**^;;;;;;r/   reply_toc                    t          |pd                                          }|r|                    d          rd S | j                            |          S )NrF   r*  )r=   r6   r  r   rr   )r   r  rS   s      r0   _reply_req_id_for_messagez&WeComAdapter._reply_req_id_for_message  sX    R((..00
 	Z228<< 	4"&&z222r/   r  c                    t          j        |           d         }|r|S t          |           j                                        dk    rdS dS )Nr   z.amrr(   r  )r  r  r   r  rP   )r  	mime_types     r0   _guess_mime_typezWeComAdapter._guess_mime_type  sP    (2215	 	>> &&((F22;))r/   c                    t          | pd                              dd          d                                                                         }t                              |          }|s|S |dv r|S |S )NrF   r  r   r   >   
text/plainr  )r=   r?   r6   rP   rV   r  )r  r  rS   guesseds       r0   _normalize_content_typez$WeComAdapter._normalize_content_type  sx    +,,223::1=CCEEKKMM
//99 	NCCCNr/   c                   t          | pd                                                                          }|                    d          rdS |                    d          rdS |                    d          s|dk    rdS d	S )
NrF   r  r{  zvideo/videozaudio/zapplication/oggre  r|  )r=   r6   rP   r  )r  r  s     r0   _detect_wecom_media_typez%WeComAdapter._detect_wecom_media_type  s    *++113399;;	)) 	7)) 	7)) 	Y:K-K-K7vr/   N	file_sizeintdetected_typec                   | dz  }t          |pd                                          }t          |pd                                                                          }| t          k    r|dd|dddd d	S |d
k    r| t          k    rddd dd|ddd	S |dk    r| t
          k    rddd dd|ddd	S |dk    r/|r|t          vrddd dd| dd	S | t          k    rddd dd|ddd	S |dd dd d	S )Ni   r|  rF   Tu   文件大小 z.2fuq   MB 超过了企业微信允许的最大限制 20MB，无法发送。请尝试压缩文件或减小文件大小。F)
final_typerejectedreject_reason
downgradeddowngrade_noter{  u   图片大小 u3   MB 超过 10MB 限制，已转为文件格式发送r  u   视频大小 re  u   语音格式 uE    不支持，企微仅支持 AMR 格式，已转为文件格式发送u   语音大小 u2   MB 超过 2MB 限制，已转为文件格式发送)r=   rP   r6   r  IMAGE_MAX_BYTESVIDEO_MAX_BYTESVOICE_SUPPORTED_MIMESVOICE_MAX_BYTES)r  r  r  file_size_mbnormalized_typenormalized_content_types         r0   _apply_file_size_limitsz$WeComAdapter._apply_file_size_limits  s    K0m5v66<<>>"%l&8b"9"9"?"?"A"A"G"G"I"I)))- DLz D D D $"&	 	 	 g%%)o*E*E$!!%""w,"w"w"w"w   g%%)o*E*E$!!%""w,"w"w"w"w   g%%& 	+BJ_+_+_"( %%)"& G(?  G  G  G   ?**"( %%)"&&zl&z&z&z&z   *!"
 
 	
r/   r  c                    |                      dd          }|dv rd S t          |                      d          pd          }d| d| S )Nr   r   r   r   zunknown errorzWeCom errcode z: )rr   r=   )r  r   r   s      r0   _response_errorzWeComAdapter._response_error   sY    ,,y!,,i4X\\(++>??3336333r/   	operationc                \    |                      |          }|rt          | d|           d S )N	 failed: )r  r   )clsr  r  r   s       r0   _raise_for_wecom_errorz#WeComAdapter._raise_for_wecom_error  sC    ##H-- 	?)==e==>>>	? 	?r/   encrypted_datar  c                J  
 | st          d          |st          d          |ddt          |          dz  z
  dz  z  z   }t          j        |          }t          |          dk    rt          dt          |                     	 ddlm}m}m} n"# t          $ r}t          d	          |d }~ww xY w ||
                    |          |                    |d d
                             }|                                }|                    |           |                                z   }	|	d         

dk     s
dk    s
t          |	          k    rt          d
           t          
fd|	
 d          D                       rt          d          |	d 
          S )Nzencrypted_data is emptyzaes_key is required=       z5Invalid WeCom AES key length: expected 32 bytes, got r   )Cipher
algorithmsmodesz3cryptography is required for WeCom media decryption   r  r   zInvalid PKCS#7 padding value: c              3  $   K   | ]
}|k    V  d S rj  r.   )r8   bytepad_lens     r0   rl  z3WeComAdapter._decrypt_file_bytes.<locals>.<genexpr>'  s'      @@4tw@@@@@@r/   z.Invalid PKCS#7 padding: padding bytes mismatch)r  r   r  r  &cryptography.hazmat.primitives.ciphersr  r  r  ImportErrorr   AESCBC	decryptorupdatefinalizer  )r  r  rU  r  r  r  r   cipherr  	decryptedr  s             @r0   r  z WeComAdapter._decrypt_file_bytes  s    	86777 	42333 CAGq(8$8A#=>>w''s88r>>_UXY\U]U]__```	_XXXXXXXXXXX 	_ 	_ 	_TUU[^^	_ 
s++UYYs3B3x-@-@AA$$&&	$$^44y7I7I7K7KK	B-Q;;'B,,'C	NN*B*BGgGGHHH@@@@9gXYY+?@@@@@ 	OMNNN7(##s   	
B 
B3B..B3r  Tuple[bytes, Dict[str, str]]c           	     `  K   ddl m}  ||          st          d|d d                    t          st	          d          | j        pt          j        dd          }|| j        u}	 |                    d	|d
dd          4 d {V 	 }|	                                 d |j
                                        D             }|                    d          }|rJ|                                r6t          |          |k    r#t          dt          |           d| d          t                      }	|                                2 3 d {V }
|	                    |
           t%          |	          |k    r#t          dt%          |	           d| d          S6 t'          |	          |fcd d d           d {V  |r|                                 d {V  S S # 1 d {V swxY w Y   	 |r|                                 d {V  d S d S # |r|                                 d {V  w w xY w)Nr   )is_safe_urlz&Blocked unsafe URL (SSRF protection): P   z*httpx is required for WeCom media downloadr!   T)r   r   GETHermesAgent/1.0z*/*)
User-AgentAcceptr   c                >    i | ]\  }}|                                 |S r.   )rP   )r8   rU  r2   s      r0   
<dictcomp>z7WeComAdapter._download_remote_bytes.<locals>.<dictcomp>D  s&    YYY*#u399;;YYYr/   zcontent-lengthz"Remote media exceeds WeCom limit: z	 bytes > z bytesz4Remote media exceeds WeCom limit while downloading: )tools.url_safetyr  r  r-   r   r   r   r   streamraise_for_statusr   r  rr   isdigitr  	bytearrayaiter_bytesrS  r   r  r   )r   r  r  r  clientcreated_clientr  r   content_lengthr   chunks              r0   r  z#WeComAdapter._download_remote_bytes,  so     
 	100000{3 	RPc#2#hPPQQQ 	MKLLL"\e&7W[&\&\&\t'88	&}}"3#  %   , , , , , , , , ))+++YY@P@V@V@X@XYYY!(-=!>!>! n&<&<&>&> 3~CVCVYbCbCb$lS=P=Pll[dlll   !{{#+#7#7#9#9       %KK&&&4yy9,,(xSVW[S\S\xxgpxxx   - $: T{{G+1, , , , , , , , , , , , ,4  &mmoo%%%%%%%%&5, , , , , , , , , , , , , , ,4  &mmoo%%%%%%%%%& &~ &mmoo%%%%%%%%&s>   +!H B;GFAG,H 
G&&H )G&*H H-media_sourcec                P    t          t          | pd                    }|j        dv S )NrF   >   httphttps)r   r=   scheme)r"  parseds     r0   _looks_like_urlzWeComAdapter._looks_like_urlX  s*    #l0b1122} 111r/   	file_nameTuple[bytes, str, str]c                4  K   t          |pd                                          }|st          d          t          j        d|          rt          d|           t          |          }|j        dv r|                     |t                     d {V \  }}|	                    d          }|p*| 
                    |||	                    dd                    }|                     |	                    dd          |          }	||	|fS |j        d	k    r4t          t          |j                                                            }
n!t          |                                          }
|
                                s(t          j                    |
z                                  }
|
                                r|
                                st+          d
|
           |
                                }|p|
j        }|                     d|          }	||	|fS )NrF   zmedia source is requiredz	<[^>\n]+>z:Media placeholder was not replaced with a real file path: >   r$  r%  r  r  r  r|  zMedia file not found: )r=   r6   r  rH   	fullmatchr   r&  r  r  rr   r  r  r   r   r  
expanduseris_absolutecwdresolveexistsis_fileFileNotFoundError
read_bytesr   )r   r"  r)  r.  r'  r   r   r  resolved_namer  
local_paths              r0   _load_outbound_mediaz!WeComAdapter._load_outbound_media]  s!     
 \'R((..00 	97888<f-- 	dbZ`bbccc&!!=---"&"="=fPb"="c"cccccccMD'")++.C"D"D%{)=)=fFY[b[f[fguwy[z[z){){M77NTV8W8WYfggL}44=F""gfk2233>>@@JJf0022J%%'' 	=(**z1::<<J  "" 	K**<*<*>*> 	K#$IZ$I$IJJJ$$&&!4Z_33BFF\=00r/   c                   K   |                      ||           d {V \  }}}|                     |          }|                     t          |          ||          }||||d|S )Nr)  )r   r  r)  r  )r7  r  r  r   )r   r"  r)  r   r  r5  r  
size_checks           r0   _prepare_outbound_mediaz$WeComAdapter._prepare_outbound_media  s      
 372K2KLdm2K2n2n,n,n,n,n,n,n)lM55lCC11#d))]LYY
(&*	
 

 
 	
r/   
media_typec           
       K   |st          d          t          |          }|t          z   dz
  t          z  }|t          k    rt          d| dt           d          |                     t
          ||||t          j        |                                          d           d {V }| 	                    |d           t          |                    d          t                    r|                    d          ni }t          |                    d	          pd
                                          }|st          d|           t!          t#          d|t                              D ]z\  }	}
||
|
t          z            }|                     t$          ||	t'          j        |                              d          d           d {V }| 	                    |d|	            {|                     t,          d	|i           d {V }| 	                    |d           t          |                    d          t                    r|                    d          ni }t          |                    d          pd
                                          }|st          d|           t          |                    d          p|          ||                    d          dS )NzCannot upload empty mediar   zFile too large: z chunks exceeds maximum of z chunks)r   r  
total_sizetotal_chunksmd5zmedia upload initr   	upload_idrF   z8media upload init failed: missing upload_id in response r   ascii)rA  chunk_indexbase64_datazmedia upload chunk zmedia upload finishmedia_idz9media upload finish failed: missing media_id in response r   
created_at)r   rE  rF  )r  r   UPLOAD_CHUNK_SIZEMAX_UPLOAD_CHUNKSr  APP_CMD_UPLOAD_MEDIA_INIThashlibr@  	hexdigestr  r>   rr   r}   r=   r6   r   	enumeraterangeAPP_CMD_UPLOAD_MEDIA_CHUNKr  	b64encodedecodeAPP_CMD_UPLOAD_MEDIA_FINISH)r   r   r<  r  r>  r?  init_response	init_bodyrA  rC  startr!  chunk_responsefinish_responsefinish_bodyrE  s                   r0   _upload_media_bytesz WeComAdapter._upload_media_bytes  sT      	:8999YY
"%66:?PP+++f<ffL]fff   #00%"$( ,{4((2244 	
 	
 	
 	
 	
 	
 	
 	
 	##M3FGGG1;M<M<Mf<U<UW[1\1\dM%%f---bd		k228b99??AA	 	kiZgiijjj"+E!ZAR,S,S"T"T 	] 	]K):!::;E#'#5#5*!*#.#)#3E#:#:#A#A'#J#J	 $ $      N ''8[k8[8[\\\\ $ 2 2')$!
 !
 
 
 
 
 
 
 	##O5JKKK5?@S@STZ@[@[]a5b5bjo))&111hj{z228b99??AA 	nl[jllmmm //=:>> %//,77
 
 	
r/   rE  c           
        K   |                      t          d|d||d|ii           d {V }|                     |d           |S )Nr"  ra  rE  zsend media message)r  APP_CMD_SENDr  )r   r&  r<  rE  r  s        r0   _send_media_messagez WeComAdapter._send_media_message  ss      ++':Z2
 
 
 
 
 
 
 
 	##H.BCCCr/   rd  c                   K   |                      |dd|d | j                 id           d {V }|                     |d           |S )Nmarkdownrd  )ra  r]  zsend reply markdown)r  MAX_MESSAGE_LENGTHr  )r   r  rd  r  s       r0   _send_reply_markdownz!WeComAdapter._send_reply_markdown  s}      11%&0H1H0H(IJ 
 
 
 
 
 
 
 
 	##H.CDDDr/   c                z   K   |                      |d||d|ii           d {V }|                     |d           |S )Nra  rE  zsend reply media message)r  r  )r   r  r<  rE  r  s        r0   _send_reply_media_messagez&WeComAdapter._send_reply_media_message  sp       11:Z2
 
 
 
 
 
 
 
 	##H.HIIIr/   Optional[SendResult]c                   K   |sd S |                      |||           d {V }|j        s&t                              d| j        |j                   |S )Nr&  rd  r  z'[%s] Follow-up markdown send failed: %s)sendsuccessr   r   r   r   )r   r&  rd  r  results        r0   _send_followup_markdownz$WeComAdapter._send_followup_markdown  sk        	4yy'HyUUUUUUUU~ 	_NNDdiQWQ]^^^r/   captionr   c                  K   |st          dd          S 	 |                     ||           d {V }n# t          $ r(}t          dt          |                    cY d }~S d }~wt          $ rJ}t
                              d| j        ||           t          dt          |                    cY d }~S d }~ww xY w|d         r>|                     |d|d          |	           d {V  t          d|d                   S | 	                    |          }|s|| j
        v r| j
        |         }	 |                     |d
         |d         |d                    d {V }	|r*|                     ||d         |	d                    d {V }
n)|                     ||d         |	d                    d {V }
nx# t          j        $ r t          dd          cY S t          $ rJ}t
                              d| j        ||           t          dt          |                    cY d }~S d }~ww xY wd }d }|r|                     |||	           d {V }|d         r/|d         r'|                     |d|d          |	           d {V }t          d|                     |
          pt#          j                    j        d d         |	|
|r|j        nd |r|j        s|j        nd |r|j        nd |r|j        s|j        nd d          S )NFchat_id is requiredrf  r   r9  z,[%s] Failed to prepare outbound media %s: %sr  u   ⚠️ r  )r  r   r  r)  rE  zTimeout sending media to WeComz [%s] Failed to send media %s: %sr  r  u   ℹ️ Tr  )uploadr  ri  caption_error	downgradedowngrade_errorrf  r0  raw_response)r   r;  r3  r=   r   r   r   r   rh  r  r   rX  ra  r[  r   r   r   r   r   r   rr  rf  )r   r&  r"  ri  r)  r  preparedr   r  upload_resultmedia_responsecaption_resultdowngrade_results                r0   _send_media_sourcezWeComAdapter._send_media_source  s       	Je3HIIII	=!99,R[9\\\\\\\\HH  	= 	= 	=e3s88<<<<<<<<< 	= 	= 	=LLGT`befffe3s88<<<<<<<<<	= J 	N..5(?355! /         
 e8O3LMMMM55h?? 	<4+B B B27;L	="&":": &%# #      M
  '+'E'E \*!*-( ( " " " " " " (,'?'?\*!*-( ( " " " " " "
 # 	U 	U 	Ue3STTTTTT 	= 	= 	=LL;TYVYZZZe3s88<<<<<<<<<	=  	#'#?#?! $@ $ $      N
 L! 	h/?&@ 	%)%A%A6(#3466! &B & &             ++N;;Ttz||?OPSQSPS?T'':HR>66d9G!pP^Pf!p!5!5lp>NX-::TX=M#xVfVn#x#3#9#9tx 
 
 
 	
sQ   5 
B9A"B9"B9/?B4.B94B90BF5 5 H*	H* ?H%H*%H*metadatac           	       K   ~|st          dd          S 	 |                     |          }|s|| j        v r| j        |         }|r|                     ||           d{V }n4|                     t
          |dd|d| j                 id           d{V }nw# t          j        $ r t          dd          cY S t          $ rI}t                              d	| j        |           t          dt          |                    cY d}~S d}~ww xY w|                     |          }|rt          d|          S t          d
|                     |          pt!          j                    j        dd         |          S )z?Send markdown to a WeCom chat via proactive ``aibot_send_msg``.Frk  rl  Nr]  rd  )r"  ra  r]  z Timeout sending message to WeComz[%s] Send failed: %sTr  rq  )r   r  r   r_  r  rZ  r^  r   r   r   r   r   r   r=   r  r   r   r   r   )	r   r&  rd  r  ry  r  r  r   r   s	            r0   re  zWeComAdapter.sendQ  s       	Je3HIIII	=99(CCL @Gt/F$F$F#6w? 
!%!:!:<!Q!QQQQQQQ!%!3!3 ")#-%.8P9P8P0Q$R " "       # 	W 	W 	We3UVVVVVV 	= 	= 	=LL/C@@@e3s88<<<<<<<<<	= $$X.. 	:e59999++H55N9I#2#9N!
 
 
 	
s$   B B  D;	D>DDD	image_urlc                "  K   ~|                      ||||           d {V }|j        s|                     |          s|S t                              d| j        ||j                   |r| d| n|}|                     |||           d {V S )Nr&  r"  ri  r  z3[%s] Falling back to text send for image URL %s: %srQ  rd  )rx  rf  r(  r   r   r   r   re  )r   r&  r{  ri  r  ry  rg  fallback_texts           r0   
send_imagezWeComAdapter.send_image  s       .."	 / 
 
 
 
 
 
 
 
 > 	!5!5i!@!@ 	MLdiYbdjdpqqq5<K711i111)YYwPXYYYYYYYYYYr/   
image_pathc                F   K   ~|                      ||||           d {V S Nr}  rx  )r   r&  r  ri  r  kwargss         r0   send_image_filezWeComAdapter.send_image_file  R       ,,#	 - 
 
 
 
 
 
 
 
 	
r/   	file_pathc                H   K   ~|                      |||||           d {V S )N)r&  r"  ri  r)  r  r  )r   r&  r  ri  r)  r  r  s          r0   send_documentzWeComAdapter.send_document  sU       ,," - 
 
 
 
 
 
 
 
 	
r/   
audio_pathc                F   K   ~|                      ||||           d {V S r  r  )r   r&  r  ri  r  r  s         r0   
send_voicezWeComAdapter.send_voice  r  r/   
video_pathc                F   K   ~|                      ||||           d {V S r  r  )r   r&  r  ri  r  r  s         r0   
send_videozWeComAdapter.send_video  r  r/   c                   K   ~~dS )z8WeCom does not expose typing indicators in this adapter.Nr.   )r   r&  ry  s      r0   send_typingzWeComAdapter.send_typing  s      XXXr/   c                f   K   ||r)|                                                     d          rdnddS )zReturn minimal chat info.r$  r%  )r   r   )rP   r  )r   r&  s     r0   get_chat_infozWeComAdapter.get_chat_info  sB       &X7==??+E+Eg+N+NXGGTX
 
 	
r/   )rW   r   r)   r*   )r)   r   )r   r=   r)   r   )r   r   r)   r   )r   r   r)   r   )r   r=   r   r   r   r   r)   r   )
r  r=   r   r   r   r=   r   r   r)   r   )r  r=   r)   r=   )r   r   r)   r=   )rD   r   r)   r  )rI  r   r)   r=   )rI  r   r)   r   )rU  r=   r)   r   )r   r   r)   r_  )r   r   r)   ry  )r  r=   r  r   r)   r  )r   r=   r)   r  )r   r  r)   r=   )r  )r  r=   r  r=   r)   r=   )r  r=   r  r=   r  r=   r)   r=   )r  r=   r  r  r  r=   r)   r=   )r   r   r,  r=   r2  r3   r)   r   )rE  r=   r)   r*   )r&  r=   rE  r=   r)   r*   )r&  r=   r)   r   )r0  r=   r   r=   r)   r   )r&  r=   r   r=   r)   r   )r  r  r)   r  )r  r=   r)   r=   )r  r=   r  r=   r)   r=   )r  r=   r)   r=   rj  )r  r  r  r=   r  r  r)   r   )r  r   r)   r  )r  r   r  r=   r)   r   )r  r  r  r=   r)   r  )r  r=   r  r  r)   r  )r"  r=   r)   r*   )r"  r=   r)  r  r)   r*  )r"  r=   r)  r  r)   r   )r   r  r<  r=   r  r=   r)   r   )r&  r=   r<  r=   rE  r=   r)   r   )r  r=   rd  r=   r)   r   )r  r=   r<  r=   rE  r=   r)   r   )r&  r=   rd  r=   r  r  r)   rb  )NNN)r&  r=   r"  r=   ri  r  r)  r  r  r  r)   r   )NN)
r&  r=   rd  r=   r  r  ry  r  r)   r   )r&  r=   r{  r=   ri  r  r  r  ry  r  r)   r   )
r&  r=   r  r=   ri  r  r  r  r)   r   )r&  r=   r  r=   ri  r  r)  r  r  r  r)   r   )
r&  r=   r  r=   ri  r  r  r  r)   r   )
r&  r=   r  r=   ri  r  r  r  r)   r   )r&  r=   r)   r   )K__name__
__module____qualname____doc__r^  SUPPORTS_MESSAGE_EDITINGr\  ro   r   r   r   r   r   r   r   r   r   r   r   REQUEST_TIMEOUT_SECONDSr  APP_CMD_RESPONSEr  staticmethodr   r   r   r  rO  rA  rT  r;  r<  r}  r  r  r  r  r  r=  propertyr  r9  r8  r  r7  r:  r  r  r  r  r  r  classmethodr  r  r  r(  r7  r;  rX  r[  r_  ra  rh  rx  re  r  r  r  r  r  r  r  __classcell__)r   s   @r0   rV   rV      s       KK+$ +5 +5 +5 +5 +5 +5b& & & &P4 4 4 4>   @ @ @ @:S S S S0Z Z Z Z6= = = =   (W W W W&6 6 6 6* * * * Tk 6 6 6 6 6& $0A A A A A4 . . . \.    \ > > > \>K- K- K- K-b
 
 
 

 
 
 
<%> %> %> %>N 0R 0R 0R \0Rd-( -( -( -(^0F 0F 0F 0Fd ) ) ) \) 	 	 	 \	 > > > > \>    \ 
 
 
 \
       \     X   
 
 
 

> 
> 
> 
>E E E EM M M M"3 3 3 3 * * * \*    \    \ =
 =
 =
 =
 \=
~ 4 4 4 \4 ? ? ? [?
 $ $ $ \$:*& *& *& *&X 2 2 2 \2 $(!1 !1 !1 !1 !1L $(
 
 
 
 
 8
 8
 8
 8
t
 
 
 
	 	 	 	   ( #'	    " "&#'"&Q
 Q
 Q
 Q
 Q
n #'-1,
 ,
 ,
 ,
 ,
d "&"&-1Z Z Z Z Z6 "&"&
 
 
 
 
( "&#'"&
 
 
 
 
, "&"&
 
 
 
 
( "&"&
 
 
 
 
     
 
 
 
 
 
 
 
r/   rV   z)https://work.weixin.qq.com/ai/qc/generatez-https://work.weixin.qq.com/ai/qc/query_resultz9https://work.weixin.qq.com/ai/qc/gen?source=hermes&scode=   i,  )timeout_secondsr  r  Optional[Dict[str, str]]c                `   	 ddl }ddl}n+# t          $ r t                              d           Y dS w xY wt
           d}t          ddd           	 |j                            |d	d
i          }|j        	                    |d          5 }t          j        |                                                    d                    }ddd           n# 1 swxY w Y   nE# t          $ r8}t                              d|           t          d|            Y d}~dS d}~ww xY w|                    d          pi }t!          |                    d          pd                                          }t!          |                    d          pd                                          }	|r|	s,t                              d|           t          d           dS t          d           t                       d}
	 ddl}|                                }|                    |	           |                    d           |                    d           d}
n# t          $ r Y nt          $ r Y nw xY wt.           |j                            |           }|
rt          d|            n"t          d| d           t          d           t                       t          ddd           t5          j                    | z   }t8           d|j                            |           }d}t5          j                    |k     rN	 |j                            |d	d
i          }|j        	                    |d           5 }t          j        |                                                    d                    }ddd           n# 1 swxY w Y   nK# t          $ r>}t                              d!|           t5          j        t>                     Y d}~d}~ww xY w|d"z  }t          d#dd           |                    d          pi }t!          |                    d$          pd                                           }|d%k    rt                       |                    d&          pi }t!          |                    d'          p|                    d(          pd                                          }t!          |                    d)          pd                                          }|r|r||d*S t          !                    d+|           t          d,           dS t5          j        t>                     t5          j                    |k     Nt                       t          d-| d.z   d/           dS )0uF  Run the WeCom QR scan flow to obtain bot_id and secret.

    Fetches a QR code from WeCom, renders it in the terminal, and polls
    until the user scans it or the timeout expires.

    Returns ``{"bot_id": ..., "secret": ...}`` on success, ``None`` on
    failure or timeout.

    Note: the ``work.weixin.qq.com/ai/qc/{generate,query_result}`` endpoints
    used here are not part of WeCom's public developer API — they back the
    admin-console web UI's bot-creation flow and may change without notice.
    The same pattern is used by the feishu/dingtalk QR setup wizards.
    r   Nz$urllib is required for WeCom QR scanz?source=hermesz  Connecting to WeCom...rF   T)endflushr  r  r     r   zutf-8z%WeCom QR: failed to fetch QR code: %sr  r   scodeauth_urlz(WeCom QR: unexpected response format: %sz# failed: unexpected response formatz done.F)fit)invertz8
  Scan the QR code above, or open this URL directly:
  z+  Open this URL in WeCom on your phone:

  rQ  zH  Tip: pip install qrcode  to display a scannable QR code here next timez#  Fetching configuration results...z?scode=r$   zWeCom QR poll error: %sr   r  statusrf  bot_infobotidrY   r[   )rY   r[   zFWeCom QR: scan reported success but bot_info missing or incomplete: %sz  QR scan reported success but no bot credentials were returned.
  This usually means the bot was not actually created on the WeCom side.
  Falling back to manual credential entry.z  QR scan timed out (r&   z minutes). Please try again.)"urllib.requesturllib.parser  r   r   _QR_GENERATE_URLprintrequestRequesturlopenr  r  readrP  r   rr   r=   r6   qrcodeQRCodeadd_datamakeprint_ascii_QR_CODE_PAGEparserh  r   	monotonic_QR_QUERY_URLr   r   _QR_POLL_INTERVALrP   r   )r  urllibgenerate_urlreqresprD   r   r   r  r  qr_rendered_qrcodeqrpage_urlr   	query_url
poll_countrg  result_datar  r  rY   r[   s                          r0   qr_scan_for_bot_infor    sw   "   ;<<<tt '666L 

$"D9999n$$\LJ[;\$]]^##C#44 	:*TYY[[//8899C	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	: 	:   <cBBB#   ttttt
 776?? bD!!'R((..00E488J''-2..4466H  ?EEE3444t	(OOO 
GGGK
    ^^
H
D
d###        <!3!3E!:!:<<H ZU8UUVVVVJhJJJKKKXYYY	GGG	
/RtDDDD ~/1H DD););E)B)BDDIJ
.

X
%
%	.((\K\<](^^C''R'88 ADDIIKK$6$6w$?$?@@A A A A A A A A A A A A A A A 	 	 	LL2C888J()))HHHH	
 	a
cr&&&&jj((.B[__X..4"55;;==YGGG"z228bHg..N(,,x2H2HNBOOUUWWFh//5266<<>>F <& <"(F;;;NNX   =  
 4
$%%%G .

X
%
%J 
GGG	
U/R"7
U
U
UVVV4s    $33:C :CC CC CC 
D!)-DD!7AI 
I+	I+*I+=:O	 7:N=1O	 =OO	 OO	 	
P4PPr  )r2   r   r)   r3   )rD   r=   r)   r=   )rL   r3   rM   r=   r)   r*   )r  r  r)   r  )Tr  
__future__r   r   r  rJ  r  loggingr  rs   rH   r   r   r   r   pathlibr   typingr   r   r	   r
   r   r  r   r   r   r,   r  r   r-   gateway.configr   r   gateway.platforms.helpersr   gateway.platforms.baser   r   r   r   r   r   	getLoggerr  r   rw   r   APP_CMD_CALLBACKAPP_CMD_LEGACY_CALLBACKr  rZ  r  r   rI  rN  rQ  r  r   r^  r   r  r   r   r   r  r  r  FILE_MAX_BYTESr  rG  rH  r  r1   rC   rK   rT   rV   r  r  r  r  _QR_POLL_TIMEOUTr  r.   r/   r0   <module>r     s   : # " " " " "          				 				   ' ' ' ' ' ' ' '       3 3 3 3 3 3 3 3 3 3 3 3 3 3 * * * * * * * *NNN   GGGLLLOO   OEEE 4 3 3 3 3 3 3 3 9 9 9 9 9 9                
	8	$	$2% ' * / & 5 7 9 %'>? )-C,DD    ! &&& ""!!#   $ 1 1 1 1
> > > >      S
 S
 S
 S
 S
& S
 S
 S
t* ? ?K  
 ,t t t t t t t ts$   A 	A)(A)-A4 4	B ?B 